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 sth3
f << 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 sth1
guard.unlock(); // 临时解锁
// do sth2
guard.lock(); // 继续上锁
// do sth3
f << 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() {
// 启动两个线程,运行函数f
std::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() {
// 启动两个线程,运行函数f
thread_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; // 原子化的int
auto 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();
}