1. thread
例子:
int main()
{
thread t1{func1, "A"};
thread t2{func2, "B"};
t1.join();
t2.join();
}
说明:
- t1和t2的第一个参数为线程要执行的内容,第二参数为参数1的参数
- thread对象在销毁之前必须join或者detach,如果没有,程序会挂掉
- 可以使用类似RAII的方法解决thread不能自动detach的问题。
2. mutex
最简单的互斥锁,用法:
lock_guard<mutex> guard{mtx}; // C++11
lock_guard guard{mtx}; // C++ 17
在guard离开作用域时自动释放锁。
3. shared_mutex
共享读写互斥锁
shared_lock guard{mtx}; // for read
unique_lock guard{mtx}; // for write
4. 使用条件变量和锁实现同步锁
void work(condition_variable &cv, int& result)
{
this_thread::sleep_for(2s); // 模拟某个耗时的工作
result = 42; // 工作的结果
cv.notify_one(); // 通知主线程工作已完成 4
}
int main()
{
condition_variable cv;
mutex cv_mut;
int result;
scope_thread th{work, ref(cv), ref(result)}; // 1
cout<<"I am waiting, now\n";
unique_lock lock{cv_mut}; // 2
cv.wait(lock); // 3
cout << "Answer: "<< result<<"\n";
return 0;
}
说明:
- 起一个线程来做某个任务。work是任务的内容,ref是包装器,用于把参数包装成引用形式
- 惯用法,配合
condition_variable
使用 - 等待工作完成
- 潜在问题:如果在(3)之前就已经执行了(4)notify,那么它就永远也等不到了。
5. 使用future实现同步锁
例子:
int work() // 1
{
this_thread::sleep(2s);
return 42;
}
int main()
{
auto fut = async(launch::async, work); // 2
cout<<"I am waiting, now\n";
cout << "Answer: "<< result<<"\n";
}
说明:
- 以正常函数的方式来写要执行的工作
launch::async
表明以异步的方式执行
6. 指令顺序问题的原因
- 编译器优化
- 处理器乱序
6.1. 例子
初始化状态:
int x = 0, y = 0;
线程1:
x = 1;
y = 2;
线程2:
if(y == 2)
{
x = 3;
y = 4;
}
可能的结果是:
- x=1, y=2
- x=3, y=4
- x=1, y=4,因为线程1有可能是先y=2再x=1
6.2. 获取、释放语义
acquire语义:此处是一个原子读操作,且在此后面的所有读操作都不应在此之前执行
release语义:此处是一个原子写操作,且在此前面的所有写操作都不应在此之后执行
还是上面的例子:
初始化状态:
int x = 0;
atomic<int> y = 0;
}
线程1:
x = 1; // 1
y.store(2, memory_order_release); // 2
线程2:
if(y.load(memory_order_acquire) == 2)
{
x = 3;
y.store(4, memory_order_relaxed); // 3
}
说明:
- 这种写法保证(1)一定在(2)之前执行
- 只需要保证是原子写
7. thread_local
7.1. thread_local作为成员变量的用法
class Obj
{
public:
static Obj& get_instance();
private:
static thread_local Obj inst_;
};
thread_local Obj obj::inst_; // 每个线程创建时构造inst_
Obj& Obj::get_instance()
{
return inst_;
}
7.2. thread_local作为普通变量的用法
class Obj
{
public Obj& get_instance();
};
Obj &Obj::get_instance() // 每个线程第一次调用次函数时创建对象
{
thread_local Obj inst;
retun inst;
}
8. 多线程性能优化
- 多线程加锁和竞争是性能杀手
- 能使用atomic就不要使用mutex,例如count++
- 如果读比写多,建议使用
shared_mutex
代替mutex - 使用
thread_local
变量