头文件#include <Thread>
及原子操作
在C++11中,<Thread>
头文件包含了Thread类,提供线程的管理。
原子操作:不可被中断的一个或者一系列操作,这些操作要一次性执行完毕,或者一个都不执行。
多线程存在的问题
在多线程中,由于进程的多个线程都是共享该进程的所有资源,那么如果有多个线程访问同一个资源时,可能会出现问题。
如果多个线程都是只读操作,那么数据不会被修改,每个进程读到的数据都是一致的,得到的结果自然也是正确的;如果涉及到数据的修改,那么多个进程读取的结果可能会出现意想不到的情况
Demo
#include <iostream>
#include <thread>
using namespace std;
uint64_t sum = 0;
void func(){
for(uint64_t i = 0; i < 100000; i++)
sum += i;
}
int main(){
cout << "Start:\tsum = " << sum << endl;
thread th1(func);
thread th2(func);
th1.join();
th2.join();
cout << "End:\tsum = " << sum << endl;
return 0;
}
代码和运行结果分析:
代码分析:
在func中实现从0到99999的累加,在main()中实例化两个线程,调用func,理论上得到的结果应该为9999900000。实际运行结果如下:
结果分析:
尝试几次运行后发现每次的结果都不一样,并且都不是想要的结果。
因为对sum的修改并不是原子操作,两个线程th1和th2都在同时修改sum。
在机器指令层面,sum += i
操作被分割成两步①先保存i的值到寄存器,②再将该寄存器的值+sum,然后保存到sum中。
在两步的间隙中,多线程的问题便体现出来。
原子操作
在C++11中,加入了新的头文件<atomic>
,这是一个模板类,其中定义了数据类型以及重载操作符等。
之前定义的sum类型是uint64_t,即unsigned long long类型,在atomic头文件包含的头文件<atomic_basic.h>
中,
...
/// atomic_uint
typedef __atomic_base<unsigned int> atomic_uint;
/// atomic_long
typedef __atomic_base<long> atomic_long;
/// atomic_ulong
typedef __atomic_base<unsigned long> atomic_ulong;
/// atomic_llong
typedef __atomic_base<long long> atomic_llong;
/// atomic_ullong
typedef __atomic_base<unsigned long long> atomic_ullong;
...
所以用新的类型atomic_ullong声明sum变量。
修改后的demo
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
atomic_ullong sum = {0};
void func(){
for(uint64_t i = 0; i < 100000; ++i)
sum += i;
}
int main(){
cout << "Start: sum = " << sum << endl;
thread th1(func);
thread th2(func);
th1.join();
th2.join();
cout << "End: sum = " << sum << endl;
return 0;
}
运行结果如下:
因为sum变量声明atomic,所以为原子操作,多个线程之间不会干扰,结果正确
修改方案2
#include <iostream>
#include <thread>
using namespace std;
uint64_t sum = 0L;
void func(){
for(uint64_t i = 0; i < 100000; i++)
sum += i;
}
int main(){
cout << "Start:\tsum = " << sum << endl;
thread th1(func);
th1.join();
thread th2(func);
th2.join();
cout << "End:\tsum = " << sum << endl;
return 0;
}
对两个线程分别用join操作,也可以得到正确的结果。
这里没有用原子操作,而是用线程的阻塞,保证th1线程在执行的过程中,th2无法访问th1所占用的资源。最终可以得到正确的结果。
join操作的阻塞相关参考:https://blog.youkuaiyun.com/hl_zzl/article/details/84637415