7.16.1多线程
C++11标准库提供std::thread类表示线程。而线程最大的作用就是在时间线上独立执行动作。所以构造thread对象是,入参必须是指定的动作,比如函数指针,函数对象,lambda,function对象等等。
thread类提供“join()”方法用于等待线程对象所代表的线程执行完毕。
可以看到输出,有混乱。
7.16.2 线程同步-互斥体
我们希望多线程并发输出内容可以交叉,但每一次cout输出的内容必须保持完整。
C++11提供std::mutex(互斥体)类,可以实现多线程共享的资源,在某一时间范围内,最多只有一个线程在访问。互斥体提供“lock()(上锁)”和“unlock()(解锁)”两个方法。某个线程在执行某一段代码前,如果成功使用某互斥体对象进行lock操作,后续如果其他线程也想使用某一互斥体执行加锁操作,则该线程将卡在这把锁面前,直到该互斥体调用unlock炒作。
注意,同一个互斥体对象可以在多处代码使用,一旦一个线程在一个地方通过某互斥体进行加锁,则互斥体在其他代码处也将形成屏障,不允许其他线程进入。下面为foo1和foo2中cout的使用范围加上锁:
#include <iostream>
#include <thread> //线程
#include <mutex> //互斥体所在头文件
using namespace std;
std::mutex output_mutex; //定义一个全局变量
void foo1()
{
for(int i = 0; i < 200; ++ i)
{ /**< 互斥体提供lock(),加锁,和unlock(),解锁两个方法 */
output_mutex.lock(); //加锁1
cout << "a-" << i << endl;
output_mutex.unlock(); //解锁1
}
}
void foo2()
{
for(int i = 0; i < 200; ++ i)
{
output_mutex.lock(); //加锁2
cout << "b-" << i << endl;
output_mutex.unlock(); //解锁2
}
}
int main()
{
thread trd_1(&foo1); //本次线程构造入参:一个函数地址
thread trd_2(&foo2); //同上
trd_1.join(); //等待trd_1所代表的线程执行完毕
trd_2.join(); //等待trd_2多代表的线程执行完毕
return 0;
}
加互斥锁之后的输出效果:
再举一例,这次我们定义一个初始整数变量“global_I”,然后写一个函数折腾该数值50万次:
int global_I = 0; //其实全局变量会自动初始化
void make_change()
{
for(int i = 0; i < 500000; ++ i)
{
if(global_I % 2 != 0)
{
global_I--; //是奇数减1
}
else
{
global_I++; //是偶数加1
}
}
}
尽管每调用一次make_change(),全局变量global_I都要被折腾50万次,但折腾的结果它还是0。为了测试这一结论,先仅用单一线程调用:
int main()
{
cout << "global_I = " << global_I << endl;
thread trd_1(&make_change);
// thread trd_2(&make_change);
trd_1.join();
// trd_2.join();
cout << "global_I = " << global_I << endl;
return 0;
}
接着将代码中的两行注释恢复为正常语句,再多执行几次,这下可好,global_I一会儿是-1,一会儿是0,一会儿是3 ……原因分析再后面
int main()
{
cout << "global_I = " << global_I << endl;
thread trd_1(&make_change);
thread trd_2(&make_change);
trd_1.join();
trd_2.join();
cout << "global_I = " << global_I << endl;
return 0;
}
解决这一问题,仍然可以通过加互斥处理:
int global_I = 0; //其实全局变量会自动初始化
mutex global_I_mutex;
void make_change()
{
for(int i = 0; i < 50000; ++ i)
{
global_I_mutex.lock();
if(global_I % 2 != 0)
{
global_I--; //是奇数减1
}
else
{
global_I++; //是偶数加1
}
global_I_mutex.unlock();
}
}
再次编译,运行,可以感觉到程序运行变慢了,不过结果正确了。程序之所以变慢也好理解:没有加锁是,两个线程同时在调用make_change ()函数,但各跑各的;加上锁喉,“加锁区”同一时刻只能有一个线程可以经过,另一个线程在一边等着。
【重要】:多线程处理一定比单线程快吗?
不一定,以前述代码为例,50万次等待额外损耗的时间,事实上将超出判断处理global_I的时长。可以试试直接在主线程中调用两次make_change(),其速度会远远快于加锁后处理双线程各自调用一次make_change()函数的速度。
上面,不加互斥锁,导致global_I不为0的原因分析: