如果对进程线程了解尚未深刻,那么可以先查看(进程与线程 | Coding中。。。 (jiuriri.com))并查阅一定的资料后,再一起学习C++11中多线程的新特性
C++中的多线程
std::thread
C++11中新标准库多出来的东西,先介绍基础用法
thread()
:默认构造函数创建线程,括号内加函数名与变量表示线程执行的内容
thread(solve,100)
执行solve(100)
函数
join()
:等待线程完成并将其内存释放,进行这一语句期间会将主进程堵塞
detach()
:不等待线程完成,直接将其内存释放
每一个线程必须绑定join或者detach,不然引发异常
#include <iostream>
#include <thread>
using namespace std;
void countnumber(int id, unsigned int n) {
for (unsigned int i = 1; i <= n; i++);
cout << "Thread " << id << " finished!" << endl;
}
int main() {
thread th[10];
for (int i = 0; i < 10; i++)
th[i] = thread(countnumber, i, 100000000);
for (int i = 0; i < 10; i++)
th[i].join();
return 0;
}
程序创建了十个线程,加入了for (unsigned int i = 1; i <= n; i++);
这样一个空循环,这样一个长空循环远远拉大了每一个线程的执行时间(不然结果会因为线程异步而变得十分混乱),异步操作执行时不一定会按照一定的顺序。
多线程的进行必定具有多线程数据操作的效果,也就是说thread
从结果上看是传引用而不是传参数,从源代码上看就是如此。
template <class Fn, class... Args>
explicit thread(Fn&& fn, Args&&... args)
使用右值引用使得不同线程在同时操作一个数据的时候会实时更新数据,而不是纯粹对传过去的数据副本进行操作,所以说以下程序会报错:
#include <iostream>
#include <thread>
using namespace std;
template<class T> void changevalue(T &x, T val) {
x = val;
}
int main() {
thread th[100];
int nums[100];
for (int i = 0; i < 100; i++)
th[i] = thread(changevalue<int>, nums[i], i+1);
for (int i = 0; i < 100; i++) {
th[i].join();
cout << nums[i] << endl;
}
return 0;
}
将th[i] = thread(changevalue<int>, nums[i], i+1);
改为th[i] = thread(changevalue<int>, ref(nums[i]), i+1);
问题解决
std::atomic和std::mutex
尽管用到了右值引用,但多个线程同时操作同一个数据的结果并不理想,如下:
#include <iostream>
#include <thread>
using namespace std;
int n = 0;
void count10000() {
for (int i = 1; i <= 10000; i++)
n++;
}
int main() {
thread th[100];
for (thread &x : th)
x = thread(count10000);
for (thread &x : th)
x.join();
cout << n << endl;
return 0;
}
理想状态下,100个线程n++10000次的结果应该是1000000,但结果总是缺斤少两。
具体原理并不追究,总之多线程同时进行操作总会出现一些操作丢失的问题。
std::mutex
是 C++11 中最基本的互斥量,一个线程将mutex锁住时,其它的线程就不能操作mutex,直到这个线程将mutex解锁。
mutex mtx;
void count10000() {
for (int i = 1; i <= 10000; i++) {
mtx.lock();
n++;
mtx.unlock();
}
}
但是这样的处理方式有点慢,就好比地铁站的安检口,增加安全性的同时减缓了乘客步伐。
std::atomic
在多线程重点 用法一般是将操作对象设置为atomic的操作对象,而atomic是原子操作,也就是说atomic必须被同步操作,这样的话借助原子操作可以避免反复进行上锁开锁操作,相当于安检口还是那个安检口,但这次乘客自觉安检而不需要安保人员干预,效率当然快很多
std::async
定义在future头文件中
是一个函数而不是类,用于更加方便地创建进程
async(solve,100)
即在新的线程中运行solve(100)
async(launch::async,solve,100)
表示异步启动线程,如果参数换为launch::deferred则与future::get,future::wait同步启动,如果是launch::async | launch::defereed则根据操作系统定(默认也是这个)
std::future
创建的线程函数有时候存在返回值,这个时候用future类将其捕获
#include <iostream>
// #include <thread> // 这里我们用async创建线程
#include <future> // std::async std::future
using namespace std;
template<class … Args> decltype(auto) sum(Args&&… args) {
// C++17折叠表达式
// "0 +"避免空参数包错误
return (0 + … + args);
}
int main() {
// 注:这里不能只写函数名sum,必须带模板参数
future<int> val = async(launch::async, sum<int, int, int>, 1, 10, 100);
// future::get() 阻塞等待线程结束并获得返回值
cout << val.get() << endl;
return 0;
}
get()
等待线程结束并获取返回值wait()
等待线程结束
据说哪怕返回值是void也最好使用future,因为future还可以检测线程是否已经结束、阻塞等待。
从别人那偷的测定应用程序加载时间的代码
// Compiler: MSVC 19.29.30038.1
// C++ Standard: C++17
#include <iostream>
#include <future>
using namespace std;
void count_big_number() {
// C++14标准中,可以在数字中间加上单
// 引号 ' 来分隔数字,使其可读性更强
for (int i = 0; i <= 10'0000'0000; i++);
}
int main() {
future<void> fut = async(launch::async, count_big_number);
cout << "Please wait" << flush;
// 每次等待1秒
while (fut.wait_for(chrono::seconds(1)) != future_status::ready)
cout << '.' << flush;
cout << endl << "Finished!" << endl;
return 0;
}
std::promise
thread创造出来的线程是获取不了返回值的,甚至join()都是void类型,只能通过传递引用的方式来获取返回值。
这个很像一个函数需要获取多个返回值的情况,那么就用引用或者指针。
解释如下:其作用是在一个线程t1中保存一个类型typename T的值,可供相绑定的std::future对象在另一线程t2中获取。
#include <iostream>
#include <future>
#include <chrono>
void Thread_Fun1(std::promise<int> &p)
{
std::this_thread::sleep_for(std::chrono::seconds(5));
int iVal = 233;
std::cout << "传入数据(int):" << iVal << std::endl;
p.set_value(iVal);
}
void Thread_Fun2(std::future<int> &f)
{
auto iVal = f.get(); //iVal = 233
std::cout << "收到数据(int):" << iVal << std::endl;
}
int main()
{
std::promise<int> pr1;
std::future<int> fu1 = pr1.get_future();
std::thread t1(Thread_Fun1, std::ref(pr1));
std::thread t2(Thread_Fun2, std::ref(fu1));
t1.join();
t2.join();
return 1;
}
iVal在线程一中被创建并赋值,用引用的方式传入线程2内
std::this_thread
一个名字空间,其中包括各种函数,用于实现对线程自己控制
例如
this_thread::get_id()
获取当前进程id
this_thread::sleep_for(//时间//)
等待一段时间