news 2026/7/23 18:32:42

线程同步——互斥和条件变量

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
线程同步——互斥和条件变量

文章目录

  • 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)

加锁后必须在任何退出的地方解锁,否则输出结果,程序挂死

abcdef

1.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_mutexstd::timed_mutex

try_lock_for

2 读写锁

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:abcdef

3. 锁保护

在加锁时,最大的危险不是不会加锁,而是在使用完毕后忘记释放锁。本章节将介绍几种更安全的加锁方式。

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&para){plockInst=&para;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;}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/23 18:23:58

3an推客靠谱吗?完整入驻推广实操教程(电商运营实测)

很多淘宝、拼多多中小商家都在寻找低成本站外推广渠道&#xff0c;最近经常有人问我&#xff1a;3an 推客靠谱吗&#xff1f;适合什么样的店铺&#xff1f;完整推广流程怎么操作&#xff1f; 作为实操多年电商运营&#xff0c;本文客观测评 3an 推客平台模式、优缺点、适用类目…

作者头像 李华
网站建设 2026/7/23 18:22:56

RAG与微调双引擎架构在金融问答系统中的实践

1. 项目概述&#xff1a;RAG与微调双引擎架构的价值去年我们团队接手了一个金融行业的智能问答系统项目&#xff0c;客户要求系统不仅能准确回答专业问题&#xff0c;还要能理解行业术语和业务场景。经过多次技术选型讨论&#xff0c;我们最终采用了RAG&#xff08;检索增强生成…

作者头像 李华
网站建设 2026/7/23 18:16:59

场外个股期权9090结构适用场景解析:参与比例、净期权费与市场观点

文章来源&#xff1a;期小衍前面几篇文章&#xff0c;我们已经连续讲过90结构、9090结构、参与比例和盈亏平衡点。很多朋友理解到这里&#xff0c;会继续问一个更实际的问题&#xff1a;既然9090结构可以通过参与比例调整成本&#xff0c;那是不是权利金越低越好&#xff1f;如…

作者头像 李华