目录
- std::unique_lock类模板
- 仅调用一次
- 线程局部存储
- 原子变量
- 往期内容回顾
std::unique_lock类模板
互斥锁保证了线程间的同步,却将并行操作变成了串行操作,对性能有较大影响,所以我们要尽可能减小锁的区间粒度。
lock_guard
只能保证在析构的时候进行解锁操作,所以其本身并没有提供加锁解锁的接口
class logFile{
private:std::mutex mtx;ofstream f;
public:logFile() {f.open("log.txt");}~logFile() {f.close();}void sharedPrint(string msg, int id) {{std::lock_guard<std::mutex> guard(mtx);// do sth1}// do sth2// ...{std::lock_guard<std::mutex> guard(mtx);// do sth3f << msg << id << endl;cout << msg << id << endl;}}
};
上面代码中,sharedPrint函数内部有两段代码需要进行加锁保护,此时使用lock_guard就需要创建两个局部对象来管理同一个互斥锁。
其实可以使用unique_lock,它提供了**lock()和unlock()**接口,能记录现在处于上锁还是没上锁状态,在析构的时候,会根据当前状态来决定是否要进行解锁(lock_guard就一定会解锁)
void sharedPrint(string msg, int id) {std::unique_lock<std::mutex> guard(mtx);// do sth1guard.unlock(); // 临时解锁// do sth2guard.lock(); // 继续上锁// do sth3f << msg << id << endl;cout << msg << id << endl;// guard.unlock(); // 这个可要可不要,因为析构的时候也会自动执行的}
仅调用一次
程序免不了要初始化数据,线程并发没有某种同步手段来控制,会导致初始化函数多次运行。
c++11种提供仅调用一次的功能,首先声明一个once_flag类型的变量作为初始化标志,最好是静态、全局的,这样对于线程就是可见的了。然后调用专门的call_once()函数,以函数式编程的方式,传递这个标志和初始化函数,这样就可以保证,即使多个线程重入call_once(),也只能由一个线程会成功初始化
#include <iostream>
#include <thread>
#include <mutex>// 全局的初始化标志
static std::once_flag flag;
auto f = []()
{std::call_once(flag,[](){std::cout << "only once" << std::endl;});
};
int main() {// 启动两个线程,运行函数fstd::thread t1(f);std::thread t2(f);t1.join();t2.join();// std::cout << "finished" << std::endl;return 0;
}
实际编译结果如下:
only once
线程局部存储
当读写全局变量时,一般也会出现数据竞争,但是全局变量不一定是必须共享的,可能仅仅是为了方便线程传入传出数据,不是共享所有权。此时可以使用关键字thread_local
实现线程局部缓存,被其标志的变量在每个线程里都会有一个独立的副本。
#include <iostream>
#include <thread>int main() {// 启动两个线程,运行函数fthread_local int n = 0;auto f = [&](int x){n += x;std::cout << n << std::endl;};std::thread t1(f, 10);std::thread t2(f, 20);t1.join();t2.join();// std::cout << "finished" << std::endl;return 0;
}
结果是:
10
20
说明两个线程互不干扰,如果将变量声明改为static,两个线程会共享这个变量,导致连续加两次,结果是下面:
10 or 20
30 30
原子变量
对于非独占、必须共享的数据,要保证多线程读写共享数据一致就要解决同步问题,两个线程得互斥。但是mutex互斥量成本较高,可以使用atmoic原子化操作。
int main() {static std::atomic_flag flag {false}; // 原子化的标志量static atomic_int n; // 原子化的intauto f = [&](){auto value = flag.test_and_set(); // TAS检查原子标志量if (value) {std::cout << "flag has been set" << std::endl;} else {std::cout << "set flag by" << std::this_thread::get_id() << std::endl; // 输出线程id}n += 100; // 原子变量加法运算std::this_thread::sleep_for(std::chrono::seconds(5)); //线程睡眠std::cout << n << std::endl;};std::thread t1(f);std::thread t2(f);t1.join();t2.join();
}
往期内容回顾
C++多线程快速入门(一):基本&常用操作