线程同步——互斥和条件变量
文章目录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 生产者消费者模式更新中本文将多线程中的一些加锁部分抽取出来深入学习C11部分有了一些崭新的库C17又支持了共享互斥锁。而C98更偏向于C语言的方式所以将C和C区分开展示案例。C语言加锁总结1. 互斥锁1.1 互斥锁1.1.1 普通互斥锁std::mutex说明基础含义只有一个线程可以占用该锁其他线程想要使用是必须等待该线程释放基本用法std::mutex mut_num;mut_num.lock();mut_num.unlock();使用场景多个线程同时对一个资源进行修改时可能发生意向不到的结果所以需要加锁以确保多个线程不会使用混乱注意事项不可重入同一个线程对同一个互斥锁加锁两次会导致死锁手动解锁使用完毕后必须手动解锁否则会导致死锁加锁代码示例//mutex.cpp#includethread#includemutex#includecstring#includechrono#includeiostreamcharg_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(inti0;iMAX_TH;i){arr1[i]std::thread(pthread_fun1);}for(inti0;iMAX_TH;i){if(arr1[i].joinable())arr1[i].join();}return0;}输出abcdef abcdef abcdef附CMakeLists.txtcmake_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不会在等待锁时空闲。代码示例#includeiostream#includeatomic#includethread#includevectorclassSpinLock{private:std::atomic_flag lock_flagATOMIC_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::coutThread id entered critical section.std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));// 模拟工作std::coutThread id leaving critical section.std::endl;spinlock.unlock();}intmain(){std::vectorstd::threadthreads;for(inti0;i5;i){threads.emplace_back(work,i);}for(autoth:threads){th.join();}return0;}1.4 定时锁1.4.1 普通定时锁std::timed_mutexstd::timed_mutex timed_mtx;voidattempt_lock_for(intid){autonowstd::chrono::steady_clock::now();if(timed_mtx.try_lock_for(std::chrono::seconds(1))){std::coutThread id got the lock.std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));timed_mtx.unlock();}else{std::coutThread id couldnt get the lock.std::endl;}}1.4.2 递归定时锁std::recursive_timed_mutex结合了std::recursive_mutex和std::timed_mutextry_lock_for2 读写锁std::shared_mutex说明头文件#include shared_mutex基础含义允许多个线程读同一个资源但只允许一个资源写入使用场景适用于读操作远多于写操作的情况注意事项可重入同一个线程对递归互斥锁加锁两次可正常加锁手动解锁使用完毕后必须手动解锁否则会导致死锁版本:C17后才可使用使用方法std::shared_mutex mut_num;mut_num.lock();mut_num.unlock();mut_num.lock_shared();mut_num.unlock_shared();mut_num.join();// 主线程阻塞等待子线程执行结束代码示例#includethread#includeshared_mutex#includecstring#includeiostreamcharg_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(inti0;iMAX_TH;i){arr1[i]std::thread(pthread_write);arr2[i]std::thread(pthread_read);}for(inti0;iMAX_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_guardstd::lock_guard可以实现锁的自动管理在作用域结束后自动释放锁。**实现原理**lock_guard类对象为局部变量在函数声明周期结束时对象的声明周期结束自动调用析构函数实现解锁。代码示例#includethread#includemutex#includecstring#includechrono#includeiostreamcharg_buf[10];std::mutex mtx;#defineMAX_TH3voidpthread_fun1(){std::lock_guardstd::mutexlock(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(inti0;iMAX_TH;i){arr1[i]std::thread(pthread_fun1);}for(inti0;iMAX_TH;i){if(arr1[i].joinable())arr1[i].join();}return0;}实现原理templatetypenameTclassself_lock{public:self_lock(Tpara){plockInstpara;plockInst-lock();}~self_lock(){if(plockInst){plockInst-unlock();}}private:T*plockInst;};3.2 唯一锁std::unique_lock也是可以自动解锁但比std::lock_guard的功能更丰富。唯一锁支持第二个参数唯一锁详解4. 条件变量和消费者模式3.1 条件变量条件变量是一种同步原语它可以阻塞一个或多个线程直到某个特定条件为真。条件变量总是与互斥锁一起使用以避免竞争条件。基本操作包括等待线程获取互斥锁成功后若条件变量不满足则释放互斥锁并进入阻塞状态直到另一个线程通知条件变量通知通知其他线程重新获取其他线程会重新获取互斥锁并重新判断条件变量是否满足条件优势避免线程获取锁成功后由于一些条件不满足而一直处于忙等待状态代码示例#includethread#includemutex#includecstring#includechrono#includevector#includeiostream#includecondition_variable#includeatomic#defineMAX_PRODUCT_NUM10std::mutex mtx;std::condition_variable cv;std::vectorintproducts(1,1);std::atomicboolstop_flag(false);// 用于控制程序退出voidfun_producer(){while(!stop_flag){std::unique_lockstd::mutexlock(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::coutmake 5std::endl;lock.unlock();cv.notify_all();std::this_thread::sleep_for(std::chrono::seconds(1));}}voidfun_consumer(intid){while(!stop_flag){std::unique_lockstd::mutexlock(mtx);cv.wait(lock,[]{returnproducts.size()0||stop_flag;});if(stop_flag){break;}if(products.size()0){products.pop_back();lock.unlock();std::coutidtakestd::endl;cv.notify_one();}else{lock.unlock();std::coutid:id no productstd::endl;cv.notify_all();}std::this_thread::sleep_for(std::chrono::seconds(1));}}intmain(){std::thread producerstd::thread(fun_producer);std::thread consumer1std::thread(fun_consumer,1);std::thread consumer2std::thread(fun_consumer,2);std::this_thread::sleep_for(std::chrono::seconds(10));// 设置停止标志stop_flagtrue;cv.notify_all();producer.join();consumer1.join();consumer2.join();return0;}3.2 生产者消费者模式使用互斥锁实现单生产者多消费者#includethread#includemutex#includecstring#includechrono#includevector#includeiostream#includecondition_variable#defineMAX_PRODUCT_NUM10std::mutex mtx;std::condition_variable cv;std::vectorintproducts(1,1);voidfun_producer(){while(1){std::unique_lockstd::mutexlock(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_lockstd::mutexlock(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::coutid:id no productstd::endl;cv.notify_all();}std::this_thread::sleep_for(std::chrono::seconds(1));}}intmain(){std::thread producerstd::thread(fun_producer);std::thread consumer1std::thread(fun_consumer,1);std::thread consumer2std::thread(fun_consumer,2);producer.join();consumer1.join();consumer1.join();return0;}