笔者近期在工作之中编程实现一个Cache结构的封装,需要使用到C++之中的互斥量Mutex,于是花了一些时间进行了调研。(结果对C++标准库很是绝望....)最终还是通过利用了Boost库的shared_mutex解决了问题。借这个机会来聊聊在C++之中的多线程编程的一些“坑”。
1.C++多线程编程的困扰
C++从11开始在标准库之中引入了线程库来进行多线程编程,在之前的版本需要依托操作系统本身提供的线程库来进行多线程的编程。(其实本身就是在标准库之上对底层的操作系统多线程API统一进行了封装,笔者本科时进行操作系统实验是就是使用的pthread或<windows.h>来进行多线程编程的)
提供了统一的多线程固然是好事,但是标准库给的支持实在是有限,具体实践起来还是让人挺困扰的:
- C++本身的STL并不是线程安全的。所以缺少了类似与Java并发库所提供的一些高性能的线程安全的数据结构。(Doug Lea大神亲自操刀完成的并发编程库,让JDK5成为Java之中里程碑式的版本)
- 如果没有线程安全的数据结构,退而求其次,可以自己利用互斥量Mutex来实现。C++的标准库支持如下的互斥量的实现:
| 互斥量 | 版本 | 作用 |
|---|---|---|
| mutex | C++11 | 最基本的互斥量 |
| timed_mutex | C++11 | 有超时机制的互斥量 |
| recursive_mutex | C++11 | 可重入的互斥量 |
| recursive_timed_mutex | C++11 | 结合 2,3 特点的互斥量 |
| shared_timed_mutex | C++14 | 具有超时机制的可共享互斥量 |
| shared_mutex | C++17 | 共享的互斥量 |
由上述表格可见,C++是从14之后的版本才正式支持共享互斥量,也就是实现读写锁的结构。由于笔者的公司仅支持C++11的版本,所以就没有办法使用共享互斥量来实现读写锁了。所以最终笔者只好求助与boost的库,利用boost提供的读写锁来完成了所需完成的工作。(所以对工具不足时可以考虑求助于boost库,确实是解放生产力的大杀器,C++的标准库实在太简陋了~~)
2.标准库互斥量的剖析
虽然吐槽了一小节,但并不影响继续去学习C++标准库给我们提供的工具.........(但愿公司能再推动升级一波C++的版本~~不过看起来是遥遥无期了)接下来笔者就要来带领大家简单剖析一些C++标准库之中互斥量。
mutex
mutex的中文翻译就是互斥量,很多人喜欢称之其为锁。其实不是太准确,因为多线程编程本质上应该通过互斥量之上加锁,解锁的操作,来实现多线程并发执行时对互斥资源线程安全的访问。 我们来看看mutex类的使用方法:
long num = 0; std::mutex num_mutex; void numplus() { num_mutex.lock(); for (long i = 0; i < 1000000; ++i) { num++; } num_mutex.unlock(); }; void numsub() { num_mutex.lock(); for (long i = 0; i < 1000000; ++i) { num--; } num_mutex.unlock(); } int main() { std::thread t1(numplus); std::thread t2(numsub); t1.join(); t2.join(); std::cout << num << std::endl; }调用线程从成功调用lock()或try_lock()开始,到unlock()为止占有mutex对象。当存在某线程占有mutex时,所有其他线程若调用lock则会阻塞,而调用try_lockh会得到false返回值。由上述代码可以看到,通过mutex加锁的方式,来确保只有单一线程对临界区的资源进行操作。
time_mutex与recursive_mutex的使用也是大同小异,两者都是基于mutex来实现的。( 本质上是基于recursive_mutex实现的,mutex为recursive_mutex的特例)
time_mutex则是进行加锁时可以设置阻塞的时间,若超过对应时长,则返回false。
recursive_mutex则让单一线程可以多次对同一互斥量加锁,同样,解锁时也需要释放相同多次的锁。
以上三种类型的互斥量都是包装了操作系统底层的pthread_mutex_t:
在C++之中并不提倡我们直接对锁进行操作,因为在lock之后忘记调用unlock很容易造成死锁。而对临界资源进行操作时,可能会抛出异常,程序也有可能break,return 甚至 goto,这些情况都极容易导致unlock没有被调用。所以C++之中通过RAII来解决这个问题,它提供了一系列的通用管理互斥量的类:
| 互斥量管理 |
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率
|
|---|
