文章目录
- 1. 互斥锁
- 1.1 互斥锁
- 1.1.1 普通互斥锁
- 加锁代码示例
- 1.1.2 递归互斥锁
- 1.3 自旋锁
- 代码示例
- 1.4 定时锁
- 1.4.1 普通定时锁
- 1.4.2 递归定时锁
- 2 读写锁
- 使用方法
- 代码示例
- 3. 锁保护
- 3.1 lock_guard
- 代码示例
- 实现原理
- 3.2 唯一锁
- 4. 条件变量和消费者模式
- 3.1 条件变量
- 3.2 生产者消费者模式
更新中
本文将多线程中的一些加锁部分抽取出来深入学习,C++11部分有了一些崭新的库,C++17又支持了共享互斥锁。而C++98更偏向于C语言的方式,所以将C和C++区分开展示案例。
C语言加锁总结
1. 互斥锁
1.1 互斥锁
1.1.1 普通互斥锁
std::mutex | 说明 |
|---|---|
| 基础含义 | 只有一个线程可以占用该锁,其他线程想要使用是必须等待该线程释放 |
| 基本用法 | std::mutex mut_num;mut_num.lock();mut_num.unlock(); |
| 使用场景 | 多个线程同时对一个资源进行修改时可能发生意向不到的结果, 所以需要加锁以确保多个线程不会使用混乱 |
| 注意事项 | 不可重入:同一个线程对同一个互斥锁加锁两次会导致死锁 手动解锁:使用完毕后必须手动解锁,否则会导致死锁 |
加锁代码示例
//mutex.cpp#include<thread>#include<mutex>#include<cstring>#include<chrono>#include<iostream>charg_buf[10];std::mutex mut_buf;#defineMAX_TH3voidpthread_fun1(){mut_buf.lock();strcpy(g_buf,"abcdef");std::this_thread::sleep_for(std::chrono::seconds(1));printf("%s\n",g_buf);mut_buf.unlock();}intmain(){std::thread arr1[MAX_TH];for(inti=0;i<MAX_TH;++i){arr1[i]=std::thread(pthread_fun1);}for(inti=0;i<MAX_TH;++i){if(arr1[i].joinable())arr1[i].join();}return0;}输出:
abcdef abcdef abcdef附CMakeLists.txt
cmake_minimum_required(VERSION3.10)project(test)set(CMAKE_CXX_STANDARD11)#set(EXECUTABLE_OUTPUT_PATH)add_executable(mutex.exe mutex.cpp)target_link_libraries(mutex.exe pthread)加锁后必须在任何退出的地方解锁,否则输出结果,程序挂死
abcdef1.1.2 递归互斥锁
std::recursive_mutex | 说明 |
|---|---|
| 基础含义 | 只有一个线程可以占用该锁,其他线程想要使用是必须等待该线程释放 |
| 基本用法 | std::recursive_mutex mut_num;mut_num.lock();mut_num.unlock(); |
| 使用场景 | 一个线程多个函数对一个资源加锁,或者在递归函数中多次对同一个资源加锁 |
| 注意事项 | 可重入:同一个线程对递归互斥锁加锁两次可正常加锁 手动解锁:使用完毕后必须手动解锁,否则会导致死锁 |
1.3 自旋锁
含义:自旋锁是指线程在等待解锁时,使线程处于忙等的锁,这意味着线程将持续占用CPU,知道加锁成功。
说明:C++标准库并没有提供专门的自选锁类型,但可以使用std::atomic_flag来实现。
使用场景:自旋锁适用于锁持有时间非常短,且线程不希望在操作系统调度中频繁上下文切换的场景。这通常用在低延迟系统中,或者当线程数量不多于CPU数量时,确保CPU不会在等待锁时空闲。
代码示例
#include<iostream>#include<atomic>#include<thread>#include<vector>classSpinLock{private:std::atomic_flag lock_flag=ATOMIC_FLAG_INIT;public:voidlock(){while(lock_flag.test_and_set(std::memory_order_acquire)){// 循环等待,直到锁变为可用状态}}voidunlock(){lock_flag.clear(std::memory_order_release);}};SpinLock spinlock;voidwork(intid){spinlock.lock();std::cout<<"Thread "<<id<<" entered critical section."<<std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));// 模拟工作std::cout<<"Thread "<<id<<" leaving critical section."<<std::endl;spinlock.unlock();}intmain(){std::vector<std::thread>threads;for(inti=0;i<5;++i){threads.emplace_back(work,i);}for(auto&th:threads){th.join();}return0;}1.4 定时锁
1.4.1 普通定时锁
std::timed_mutex
std::timed_mutex timed_mtx;voidattempt_lock_for(intid){autonow=std::chrono::steady_clock::now();if(timed_mtx.try_lock_for(std::chrono::seconds(1))){std::cout<<"Thread "<<id<<" got the lock."<<std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));timed_mtx.unlock();}else{std::cout<<"Thread "<<id<<" couldn't get the lock."<<std::endl;}}1.4.2 递归定时锁
std::recursive_timed_mutex结合了std::recursive_mutex和std::timed_mutex,
try_lock_for2 读写锁
std::shared_mutex | 说明 |
|---|---|
| 头文件 | #include <shared_mutex> |
| 基础含义 | 允许多个线程读同一个资源,但只允许一个资源写入 |
| 使用场景 | 适用于读操作远多于写操作的情况 |
| 注意事项 | 可重入:同一个线程对递归互斥锁加锁两次可正常加锁 手动解锁:使用完毕后必须手动解锁,否则会导致死锁 版本:C++17后才可使用 |
使用方法
std::shared_mutex mut_num;mut_num.lock();mut_num.unlock();mut_num.lock_shared();mut_num.unlock_shared();mut_num.join();// 主线程阻塞,等待子线程执行结束代码示例
#include<thread>#include<shared_mutex>#include<cstring>#include<iostream>charg_buf[10];std::shared_mutex mtx;#defineMAX_TH3voidpthread_write(){mtx.lock();strcpy(g_buf,"abcdef");std::this_thread::sleep_for(std::chrono::seconds(1));printf("write: %s\n",g_buf);mtx.unlock();}voidpthread_read(){mtx.lock_shared();std::this_thread::sleep_for(std::chrono::seconds(1));printf("read: %s\n",g_buf);mtx.unlock_shared();}intmain(){std::thread arr1[MAX_TH],arr2[MAX_TH];for(inti=0;i<MAX_TH;++i){arr1[i]=std::thread(pthread_write);arr2[i]=std::thread(pthread_read);}for(inti=0;i<MAX_TH;++i){if(arr1[i].joinable()){arr1[i].join();}if(arr2[i].joinable()){arr2[i].join();}}return0;}输出结果:读的线程同时输出,写的线程隔一秒输出
write:abcdef read:abcdef read:abcdef read:abcdef write:abcdef write:abcdef3. 锁保护
在加锁时,最大的危险不是不会加锁,而是在使用完毕后忘记释放锁。本章节将介绍几种更安全的加锁方式。
3.1 lock_guard
std::lock_guard可以实现锁的自动管理,在作用域结束后自动释放锁。
**实现原理:**lock_guard类对象为局部变量,在函数声明周期结束时,对象的声明周期结束,自动调用析构函数,实现解锁。
代码示例
#include<thread>#include<mutex>#include<cstring>#include<chrono>#include<iostream>charg_buf[10];std::mutex mtx;#defineMAX_TH3voidpthread_fun1(){std::lock_guard<std::mutex>lock(mtx);strcpy(g_buf,"abcdef");std::this_thread::sleep_for(std::chrono::seconds(1));printf("%s\n",g_buf);}intmain(){std::thread arr1[MAX_TH];for(inti=0;i<MAX_TH;++i){arr1[i]=std::thread(pthread_fun1);}for(inti=0;i<MAX_TH;++i){if(arr1[i].joinable())arr1[i].join();}return0;}实现原理
template<typenameT>classself_lock{public:self_lock(T¶){plockInst=¶plockInst->lock();}~self_lock(){if(plockInst){plockInst->unlock();}}private:T*plockInst;};3.2 唯一锁
std::unique_lock也是可以自动解锁,但比std::lock_guard的功能更丰富。
唯一锁支持第二个参数
唯一锁详解
4. 条件变量和消费者模式
3.1 条件变量
条件变量是一种同步原语,它可以阻塞一个或多个线程,直到某个特定条件为真。条件变量总是与互斥锁一起使用,以避免竞争条件。基本操作包括:
- 等待:线程获取互斥锁成功后,若条件变量不满足,则释放互斥锁并进入阻塞状态,直到另一个线程通知条件变量
- 通知:通知其他线程重新获取,其他线程会重新获取互斥锁,并重新判断条件变量是否满足条件
优势:避免线程获取锁成功后,由于一些条件不满足而一直处于忙等待状态
代码示例
#include<thread>#include<mutex>#include<cstring>#include<chrono>#include<vector>#include<iostream>#include<condition_variable>#include<atomic>#defineMAX_PRODUCT_NUM10std::mutex mtx;std::condition_variable cv;std::vector<int>products(1,1);std::atomic<bool>stop_flag(false);// 用于控制程序退出voidfun_producer(){while(!stop_flag){std::unique_lock<std::mutex>lock(mtx);cv.wait(lock,[]{returnproducts.size()<MAX_PRODUCT_NUM||stop_flag;});if(stop_flag){break;}products.push_back(1);products.push_back(1);products.push_back(1);products.push_back(1);products.push_back(1);std::cout<<"make 5"<<std::endl;lock.unlock();cv.notify_all();std::this_thread::sleep_for(std::chrono::seconds(1));}}voidfun_consumer(intid){while(!stop_flag){std::unique_lock<std::mutex>lock(mtx);cv.wait(lock,[]{returnproducts.size()>0||stop_flag;});if(stop_flag){break;}if(products.size()>0){products.pop_back();lock.unlock();std::cout<<id<<"take"<<std::endl;cv.notify_one();}else{lock.unlock();std::cout<<"id:"<<id<<" no product"<<std::endl;cv.notify_all();}std::this_thread::sleep_for(std::chrono::seconds(1));}}intmain(){std::thread producer=std::thread(fun_producer);std::thread consumer1=std::thread(fun_consumer,1);std::thread consumer2=std::thread(fun_consumer,2);std::this_thread::sleep_for(std::chrono::seconds(10));// 设置停止标志stop_flag=true;cv.notify_all();producer.join();consumer1.join();consumer2.join();return0;}3.2 生产者消费者模式
使用互斥锁实现,单生产者,多消费者
#include<thread>#include<mutex>#include<cstring>#include<chrono>#include<vector>#include<iostream>#include<condition_variable>#defineMAX_PRODUCT_NUM10std::mutex mtx;std::condition_variable cv;std::vector<int>products(1,1);voidfun_producer(){while(1){std::unique_lock<std::mutex>lock(mtx);cv.wait(lock,[]{returnproducts.size()<MAX_PRODUCT_NUM;});products.push_back(1);printf("make\n");lock.unlock();cv.notify_all();std::this_thread::sleep_for(std::chrono::seconds(1));}}voidfun_consumer(intid){while(1){std::unique_lock<std::mutex>lock(mtx);if(cv.wait_for(lock,std::chrono::seconds(1),[]{returnproducts.size()>0;})){products.pop_back();lock.unlock();printf("id(%u)take\n",id);cv.notify_all();}else{lock.unlock();std::cout<<"id:"<<id<<" no product"<<std::endl;cv.notify_all();}std::this_thread::sleep_for(std::chrono::seconds(1));}}intmain(){std::thread producer=std::thread(fun_producer);std::thread consumer1=std::thread(fun_consumer,1);std::thread consumer2=std::thread(fun_consumer,2);producer.join();consumer1.join();consumer1.join();return0;}