为什么要进行多线程编程
无他,更快。
多线程编程中的难点
在多线程编程环境下,计算顺序的不确定性是一个本质问题。而多线程编程的难点在于
- 线程互斥。解决数据争用的问题。多线程编程下(同一进程的)各线程共享的全局对象,由于操作系的抢占式任务调度调度方式会造成计算顺序的混乱,具体来说就是实际运行顺序并不是程序所期待的顺序。
- 线程同步。将一个大任务拆分为多个小任务(线程)后,小任务之间是需要通过某种方式组织起来还原会大任务的。具体来说,小任务之间是通过一个有向无环图组织起来的,其中某些小任务需要等待另外一些小任务的完成,否则无法继续。
如何解决线程互斥问题
在C++中,解决线程互斥(问题1)的方式有:
- lock_gurad/unique_lock + mutex
- semaphore = mutex + condition_variable + int_cnt
- atomic
解决问题2的方式有:
- condition_variable
解决数据争用问题
加锁操作
使用lock_gurad/unique_lock + mutex可实现异常安全的加锁操作。
单纯使用mutex的.lock()和.unlock()方法不是异常安全的。如每一个线程要完成run()
这样一个计算任务:
mutex m;
void run(int a, int b, int c) {
m.lock();
int d = a + b / c;
m.unlock();
}
这里使用的.lock()与.unlock()划定了一个临界区。若临界区中的计算抛异常了,就无法执行.unlock()语句。从而,mutex永远被锁上了。
上述问题的解决知道就是使用RAII手法,利用lock_guard/unique_lock持有mutex对象。lock_guard中的构造函数调用了.lock(),析构函数实现了.unlock(),从而使用lock_guard/unique_lock + mutex可实现异常安全的锁操作。具体代码如下:
mutex m;
void run(int a, int b, int c) {
lock_guard<mutex> l(m)
int d = a + b / c;
}
跨线程安全的资源计数
信号量实现了这样的功能:
- 获得一个资源,资源计数器减1
- 释放一个资源,资源计数器加1
- 没有资源可以获得,线程阻塞等待
C++原生不支持信号量,可以通过lock_guard/unique_lock + mutex + int_cnt手动实现一个。具体代码如下:
class Semaphore
{
private:
mutex mMutex;
condition_variable mCondVar;
int64_t mAvailable;
public:
explicit Semaphore(int64_t init) : mAvailable(init)
{}
void post()
{
{
unique_lock<mutex> l(mMutex);
// do computation
++mAvailable;
}
mCondVar.notify_one();
}
void wait()
{
unique_lock<mutex> l(mMutex);
while (mAvailable == 0) {
mCondVar.wait(l);
}
--mAvailable;
}
};
原子操作
c++的标准库atomic可实现原子操作。 todo
解决线程等待问题
todo
C++高级线程同步原语
future,promise,packaged_task
future实现延迟计算。特别地,future可实现线程通知。
TODO