3 原子操作
C++11中引入了原子操作。所谓原子操作:即不可被中断的一个或一系列操作,C++11引入的原子操作类型,使得线程间数据的同步变得非常高效。
👉【atomic】👈
比如之前我们抢票的代码还可以这样写:
atomic<long long> tickets; mutex mtu; int main() { thread t1([] { for (int i = 0; i < 100000; ++i) { tickets++; } }); thread t2([] { for (int i = 0; i < 100000; ++i) { tickets++; } }); t1.join(); t2.join(); cout << tickets << endl; return 0; }
运行结果:
我们使用原子操作不用加锁也可以正确的完成。其实原子操作的底层是用的CAS原子操作
来完成的。
在C++11中,程序员不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的
访问。更为普遍的,程序员可以使用atomic类模板,定义出需要的任意原子类型。
atmoic<T> t; // 声明一个类型为T的原子类型变量t
注意:原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此在C++11中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。
4 条件变量
先来看看condition_variable
类中有哪些成员函数?
这个用法与Linux中基本一致,我在这里就不再多讲了。
现在假如我们要实现下面的场景,我们究竟应该怎么做?
支持两个线程交替打印,一个打印奇数,一个打印偶数。
这样实现有两个注意点:
- 1️⃣要保证一个线程先运行,一个后运行;
- 2️⃣要防止一个线程不断运行。
- 所以我们就可以写出下面的代码:
int num = 1; mutex mtu; condition_variable cdv; int main() { thread t1([=] { for (;num<100;num++) { unique_lock<mutex> lock(mtu); if (num % 2 == 0) cdv.wait(lock); cout << this_thread::get_id() << ":" << num << endl; cdv.notify_one(); } }); thread t2([] { for (; num<100; num ++) { unique_lock<mutex> lock(mtu); if(num%2!=0) cdv.wait(lock); cout << this_thread::get_id() << ":" << num << endl; cdv.notify_one(); } }); t1.join(); t2.join(); return 0; }
但是上面的代码可能会存在着问题,因为在条件判断的时候我们没有加锁,这就导致有可能其中一个线程打印出了101这样的数据,所以我们可以写一个死循环,在循环体里面判断即可。
thread t1([=] { for (; ;num++) { unique_lock<mutex> lock(mtu); if (num >= 100) break; if (num % 2 == 0) cdv.wait(lock); cout << this_thread::get_id() << ":" << num << endl; cdv.notify_one(); } }); thread t2([] { for (; ; num++) { unique_lock<mutex> lock(mtu); if (num > 100) break; if(num%2!=0) cdv.wait(lock); cout << this_thread::get_id() << ":" << num << endl; cdv.notify_one(); } });