C++多线程中共享变量同步问题

news2024/11/27 22:24:11

目录

       1、互斥量

(1)std::mutex

(2)std::recursive_mutex 

(3)std::timed_mutex

2、锁管理器

(1)std::lock_guardlk

(2)std::unique_locklk

(3)std::unique_lock第二个参数使用

3、条件变量

(1)std::condition_variable

   a>wait()函数

   b>notify_one()函数 

   c>notify_all()函数

4、原子操作


1、互斥量

(1)std::mutex

       std::mutex是互斥量类。

常用函数:

  a>lock()函数

  b>unlock()函数

  c>try_lock()函数

  try_lock尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。调用情况有以下两种:
(1)如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用unlock释放互  斥量。
(2)如果当前互斥量被其他线程锁住,则当前调用线程返回false,而并不会被阻塞掉,继续往下执行

用法:同一个线程中同一个互斥量对象只能调用一次lock函数,否则程序会报错,lock()的次数和unlock()需要相等。用法参考如下

    mtx.lock();
    //todo 共享变量处理
    mtx.unlock();

 try_lock的demo如下:

#include <iostream>
#include <unistd.h>
#include <mutex>
#include <thread>
std::mutex mtx;
int gCnt = 0;
void Threadfun()
{
    while(true)
    {
        if(mtx.try_lock())
        {
            gCnt++;
            std::cout << "get lock " << std::endl;
            mtx.unlock();
        }else
        {
            gCnt--;
            std::cout << "can't get lock " << std::endl;
        }

    }

}

void Threadfun2()
{
    while(true)
    {

        mtx.lock();
        std::this_thread::sleep_for(std::chrono::microseconds(3000));
        mtx.unlock();
    }
}
int main()
{
    std::thread th1(Threadfun);
    std::thread th2(Threadfun2);
    th1.join();
    th2.join();
    std::cout << "gCnt " << gCnt << std:: endl;

    return 0;
}

 执行结果如下:

(2)std::recursive_mutex 

std::recursive_mutex 递归互斥量类型。

 常用函数:

  a>lock()函数

  b>unlock()函数。

用法:可以在同一个线程中队同一个互相量对象进行多次的lock()和unlock()。lock()的次数和unlock()需要相等。该互斥类型由于多次lock锁,所以性能较差。

完整的demo可参考如下:

#include <iostream>
#include <unistd.h>
#include <mutex>
#include <thread>
std::recursive_mutex recur_mtx;
int gCnt = 0;
void Threadfun()
{
    for(int i = 0;i < 10000;i++)
    {
        recur_mtx.lock();
        gCnt++;
        recur_mtx.unlock();
    }

}

void Threadfun2()
{

   recur_mtx.lock();
   Threadfun();
   recur_mtx.unlock();

}

int main()
{
    std::thread th(Threadfun2);
    th.join();
    std::cout << "gCnt " << gCnt << std:: endl;

    return 0;
}

执行结果如下:

(3)std::timed_mutex

std::timed_mutex是关于时间类型的互斥量类型,比mutex多的成员函数如下:

a> bool try_lock_for(const chrono::duration<_Rep, _Period>& __rtime)

设置一个时间rtime范围,在这一段时间范围之内线程如果没有获得锁,则保持阻塞,如果获取锁,该函数返回true;如果超时rtime时间范围,为获取锁,则返回false。

b> bool try_lock_until(const chrono::time_point<_Clock, _Duration>& __atime)
函数参数__atime表示一个时刻,在这一时刻之前线程如果没有获得锁,则保持阻塞,如果获得锁,则返回true.如果超过指定时刻__atime没有获得锁,则函数返回false。

try_lock_for的demo如下:

#include <iostream>
#include <unistd.h>
#include <mutex>
#include <shared_mutex>
#include <thread>
std::timed_mutex time_mtx;
int gCnt = 0;
void ThreadTime1()
{
    time_mtx.lock();
    std::this_thread::sleep_for(std::chrono::microseconds(2000));
    time_mtx.unlock();
}
void ThreadTime()
{
    std::chrono::microseconds dur(1000);
    if(time_mtx.try_lock_for(dur))
    {
        gCnt +=10;
    }else
    {
        gCnt +=20;
    }

    std::cout << "ThreadTime run done" << std::endl;
}
int main()
{
    std::thread th1(ThreadTime1);
    std::thread th2(ThreadTime);
    th1.join();
    th2.join();
    std::cout << "gCnt " << gCnt << std:: endl;

    return 0;
}

 执行结果如下:

 try_lock_until的demo如下:

#include <iostream>
#include <unistd.h>
#include <mutex>
#include <shared_mutex>
#include <thread>
std::timed_mutex time_mtx;
int gCnt = 0;
void ThreadTime1()
{
    time_mtx.lock();
    std::this_thread::sleep_for(std::chrono::microseconds(1000));
    time_mtx.unlock();
}
void ThreadTime()
{
    std::chrono::microseconds dur(1000);
    if(time_mtx.try_lock_until(std::chrono::steady_clock::now()+dur))
    //当前时间+1s时间内获取到锁,执行+10操作,如果超时为获取执行+20操作
    {
        gCnt +=10;
    }else
    {
        gCnt +=20;
    }

    std::cout << "ThreadTime run done" << std::endl;
}
int main()
{
    std::thread th1(ThreadTime1);
    std::thread th2(ThreadTime);
    th1.join();
    th2.join();
    std::cout << "gCnt " << gCnt << std:: endl;
    return 0;
}

执行结果如下:

 

死锁:两个锁及以上才会出现。如果多个锁的获取设计不当就出现一直获取锁的阻塞状态,使线程永远拿不到锁。eg:

std::mutex mtx1;
std::mutex mtx2;
void threadF1()
{
    mtx1.lock();
    mtx2.lock();     //线程1等待mtx2的锁
    //to do
    mtx1.unlock();
    mtx2.unlock();
}

void threadF2()
{
    mtx2.lock();
    mtx1.lock();    //线程2等待mtx1的锁
    //to do
    mtx1.unlock();
    mtx2.unlock();
}

2、锁管理器

锁管理器是遵循RAII机制,RAII(Resource Acquisition IInitialization)是由c++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化,他说:使用局部对象来管理资源的技术称为资源获取即初始化;这里的资源主要是指操作系统中有限的东西如内存、网络套接字等等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入。

(1)std::lock_guard<std::mutex>lk

std::lock_guard是模板类,是std::mutex的lock和unlock功能集成,在创建std::lock_guard对象时,会自动lock加锁,不需要手动释放锁(unlock),这样可以避免忘记解锁。但std::lock_guard对象生命周期过程中不能解锁,必须得生命周期结束时,会自动析构解锁。

(2)std::unique_lock<std::mutex>lk

std::unqiue_lock是模板类,是std::lock_gurad的升级版,它除了拥有std::lock_gurad自动析构的功能,它可以随时解锁,比std::lock_gurad使用更灵活。

std::lock_guard的 demo如下:

#include <iostream>
#include <unistd.h>
#include <mutex>
#include <thread>
#include <sys/time.h>
#include <cmath>
std::mutex mtx;
int gCnt = 0;
struct timeval start,end;
void Threadfun()
{

    std::lock_guard<std::mutex>lk(mtx);
    gCnt++; //共享操作已操作完成
    std::cout << "gCnt " << gCnt << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(10));  //相当于处理其他代码


}

void Threadfun2()
{

    std::lock_guard<std::mutex>lk(mtx);
    gCnt += 10;
    std::cout << "gCnt " << gCnt << std::endl;
    gettimeofday(&end,nullptr);

    std::cout << "use time is " << end.tv_sec + end.tv_usec * pow(10,-6)-start.tv_sec-start.tv_usec*pow(10,-6);
    std::cout <<" s" << std::endl;
}

int main()
{

    gettimeofday(&start,nullptr);

    std::thread th1(Threadfun);
    std::thread th2(Threadfun2);
    th1.join();
    th2.join();
    std::cout << "gCnt " << gCnt << std:: endl;

    return 0;
}

执行结果如下:

 std::unique_lock的demo如下:

#include <iostream>
#include <unistd.h>
#include <mutex>
#include <thread>
#include <sys/time.h>
#include <cmath>
std::mutex mtx;
int gCnt = 0;
struct timeval start,end;
void Threadfun()
{

    std::unique_lock<std::mutex>lk(mtx);
    gCnt++; //共享操作已操作完成
    std::cout << "gCnt " << gCnt << std::endl;
    lk.unlock();
    std::this_thread::sleep_for(std::chrono::seconds(10));  //相当于处理其他代码


}

void Threadfun2()
{

    std::unique_lock<std::mutex>lk(mtx);
    gCnt += 10;
    std::cout << "gCnt " << gCnt << std::endl;
    gettimeofday(&end,nullptr);

    std::cout << "use time is " << end.tv_sec + end.tv_usec * pow(10,-6)-start.tv_sec-start.tv_usec*pow(10,-6);
    std::cout <<" s" << std::endl;
}

int main()
{

    gettimeofday(&start,nullptr);

    std::thread th1(Threadfun);
    std::thread th2(Threadfun2);
    th1.join();
    th2.join();
    std::cout << "gCnt " << gCnt << std:: endl;

    return 0;
}

 执行结果如下:

 上面的demo只是简单演示,具体的使用需要根据场景灵活应用。

(3)std::unique_lock第二个参数使用

可以在创建锁管理器对象时,通过设置第二个参数来进行获取锁,具体的参数含义如下:

 std::lock_guard的第二个参数只有一个adopt_lock。

std::mutex mtx;
void Threadfun()
{

    std::lock_guard<std::mutex>lk1(mtx,std::adopt_lock);
    
    std::unique_lock<std::mutex>lk2(mtx,std::adopt_lock);
    std::unique_lock<std::mutex>l3(mtx,std::try_to_lock);
    std::unique_lock<std::mutex>lk5(mtx,std::defer_lock);
}

 第二个参数使用可以参考

c++多线程,adopt_lock_t/defer_lock_t/try_to_lock_t_lysSuper的博客-CSDN博客

c++11 std::defer_lock, std::try_to_lock, std::adopt_lock__李白_的博客-CSDN博客

3、条件变量

(1)std::condition_variable

std::condition_variable是一个类,搭配锁管理器std::unqiue_lock和互斥量std::mutex来使用,主要利用成员函数wait()、notify_one()、notify_all()函数来实现线程间执行顺序控制。

成员函数

a>wait()函数

wait函数有两种,如下:

1、void wait(unique_lock<mutex>& __lock) noexcept;
  //阻塞当前线程,等待notify_one、notify_all的唤醒后,获取锁,继续往下执行。


2、void wait(unique_lock<mutex>& __lock, _Predicate __p)
    //阻塞当前线程,等待notify_one、notify_all的唤醒,且_Predicate 判断式返回true时,才获取锁,继续往下执行。

第一种wait会存在虚假唤醒,第二种wait会避免虚假唤醒,所以应使用带2个参数的wait。

b>notify_one()函数 

通知一个线程的wait()

c>notify_all()函数

 通知所有线程的wait()

demo 如下:

#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>
#include <chrono>
#include <sys/time.h>
#include <cmath>
#include <condition_variable>
#include <vector>

unsigned long counter=0;
unsigned long long VarMax = pow(10,2);
std::vector<int>vecQue;

std::mutex mtx;
std::condition_variable cnd;
#define CONDITION
//#define MUT
void Increase(unsigned long long nCnt)
{
    std::cout << "Increase thread id  " << std::this_thread::get_id() << std::endl;
    for(unsigned long long i =0; i < nCnt;i++)
    {
#ifdef MUT
        mtx.lock();
        counter++;
        mtx.unlock();
#endif

#ifdef CONDITION
        std::unique_lock<std::mutex>lk(mtx);
        vecQue.emplace_back(i);
        cnd.notify_one();
#endif
    }
}

void OutVarFunc()
{
    while (true)
    {
#ifdef MUT
    mtx.lock();
    std::cout << "OutVarFunc  " << counter << std::endl;
    mtx.unlock();
    
#endif

#ifdef CONDITION
#if 0
    std::unique_lock<std::mutex>lk(mtx);
    cnd.wait(lk);
    int tmp = vecQue.front();
    std::cout << "OutVarFunc  " << tmp  << " size " << vecQue.size() << std::endl;
    vecQue.erase(vecQue.begin());
#else
    std::unique_lock<std::mutex>lk(mtx);
    cnd.wait(lk,[](){
        if(!vecQue.empty())
        {
            return true;
        }else{
            return false;
        }
    });

    int tmp = vecQue.front();
    std::cout << "OutVarFunc  " << tmp  << " size " << vecQue.size() << std::endl;
    vecQue.erase(vecQue.begin());
    #endif
#endif
    }

}

int main(int argc,char** argv)
{
    std::cout << "main thread id " << std::this_thread::get_id() << std::endl;

    struct timeval start,end;
    gettimeofday(&start,nullptr);

    std::thread t3(OutVarFunc);
    std::thread t1(Increase,VarMax);
    std::thread t2(Increase,VarMax);
   
    t1.join();
    t2.join();
    t3.join();
    
    gettimeofday(&end,nullptr);

    std::cout << "use time is " << end.tv_sec + end.tv_usec * pow(10,-6)-start.tv_sec-start.tv_usec*pow(10,-6);
    std::cout <<"s, counter " << counter << std::endl;

    return 0;
}

CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.0)
project(MAIN)

SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -pthread")

add_definitions("-Wall -g")

file(GLOB_RECURSE SRC_CPP ${PROJECT_SOURCE_DIR}/src/*.cpp)

add_executable(${PROJECT_NAME} ${SRC_CPP})

执行结果如下:

 

上面的demo 利用条件变量实现了一个生产者消费者的功能。 

4、原子操作

原子操作是颗粒度最小的操作,只会存在执行完成,执行未完成两种状态。所以应用在多线程中可以实现共享变量可以保证读写的互斥性和提高执行效率,原子操作的执行效率是比锁、锁管理器高好几倍。使用时需要注意不是原子操作的运算符不要使用,以免结果异常,可以通过写demo进行测试。

注意:如果原子共享变量,在变量的读写顺序上有要求,需要注意store写函数中的第二个参数std::memory_order的选择,推荐memory_order_seq_cst。

可以参考:

c++11 atomic 之 atomic 使用_atomic_init_Npgw的博客-CSDN博客

C++11多线程(五)原子操作简单使用_c++ atomic load_AczQc的博客-CSDN博客

C++ 并发指南-atomic原子变量使用struct(一)_c++原子变量用法_面-包的博客-CSDN博客

 C++高并发:原子操作和memory order - 知乎 (zhihu.com)

附加

多线程创建函数可参考:C++多线程编程(真实入门)_c++函数放入线程中执行_夜雨听萧瑟的博客-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/524664.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

掌控MySQL并发:深度解析锁机制与并发控制

前一篇MySQL读取的记录和我想象的不一致——事物隔离级别和MVCC 讲了事务在并发执行时可能引发的一致性问题的各种现象。一般分为下面3种情况&#xff1a; 读 - 读情况&#xff1a;并发事务相继读取相同的记录。读取操作本身不会对记录有任何影响&#xff0c;不会引起什么问题&…

【C++】C++中的多态

目录 一.多态的概念二.多态的定义及实现2.1虚函数2.2虚函数的重写虚函数重写的两个例外 2.3多态的构成条件2.4C11 override 和final2.5重载、重写、隐藏的对比 三.抽象类3.1概念3.2接口继承和实现继承 四.多态的原理4.1虚函数表4.2多态的原理(1)代码分析(2)清理解决方案 4.3动态…

MySQL高阶语句与连接

目录 高级查询selectDISTINCTWHEREAND ORINBETWEEN通配符与likeORDER BY数学函数聚合函数字符串函数mysql进阶查询GROUP BYHAVING别名子查询EXISTS连接查询inner join(内连接)left join(左连接)right join(右连接)自我连接 高级查询 实验准备&#xff1a; 第一张表&#xff1a…

Cesium入门之六:Cesium加载影像图层(ArcGIS、Bing、Mapbox、高德地图、腾讯地图、天地图等各类影像图)

Cesium加载影像图层 一、ImageryLayer类常用属性常用方法 二、ImageryLayerCollection类常用属性常用方法 三、ImageryProvider类常用属性常用方法 四、ImageryProvider子类1. ArcGisMapServerImageryProvider加载ArcGIS地图服务 2. BingMapsImageryProvider加载BingMap地图服务…

call to non-‘constexpr‘ function

文章目录 call to non-constexpr function概述备注END call to non-‘constexpr’ function 概述 在尝试迁移 openpnp - Smoothieware project 从gcc命令行 MRI调试方式 到NXP MCUXpresso工程. 在加了头文件路径后, 还有一些语法错误. 这和编译器语法有关系. 在运行BuildShe…

阿里云服务器部署flask项目「gunicorn + nginx + 支持https」

最近做了一个微信小程序&#xff0c;使用 flask 实现了对应的后台&#xff0c;上线需要部署到服务器上&#xff0c;之前只是了解并没有全链路试过&#xff0c;靠着网上的资料最终完成部署上线&#xff0c;但中间遇到了较多的一些问题&#xff0c;网上的资料也比较零碎&#xff…

WPF MaterialDesign 初学项目实战(2)首页导航栏样式

其他内容 WPF MaterialDesign 初学项目实战&#xff08;0&#xff09;:github 项目Demo运行 WPF MaterialDesign 初学项目实战&#xff08;1&#xff09;首页搭建 MaterialDesign 确保运行了初学项目实战&#xff08;0&#xff09; MaterialDesign给我们提供了很多的样式库&…

微服务框架【笔记-Nacos环境隔离】

Nacos注册中心 环境隔离 - namespace Nacos 中服务存储和数据存储的最外层都是一个名为namespace的东西&#xff0c;用来做最外层隔离 Nacos默认的命名空间&#xff1a; 创建命名空间复制命名空间ID启动Orderservice服务&#xff0c;在nacos服务列表可以看到环境隔离之后的服…

vue实现电梯锚点导航

1、目标效果 最近喝了不少的咖啡、奶茶&#xff0c;有一个效果我倒是挺好奇怎么实现的&#xff1a; &#xff08;1&#xff09;点击左侧分类菜单&#xff0c;右侧滚动到该分类区域 &#xff08;2&#xff09;右侧滑动屏幕&#xff0c;左侧显示当前所处的分类区域 这种功能会出现…

Jmeter进阶使用:BeanShell实现接口前置和后置操作

一、背景 我们使用Jmeter做压力测试或者接口测试时&#xff0c;除了最简单的直接对接口发起请求&#xff0c;很多时候需要对接口进行一些前置操作&#xff1a;比如提前生成测试数据&#xff0c;以及一些后置操作&#xff1a;比如提取接口响应内容中的某个字段的值。举个最常用…

XDC约束技巧 之 I/O篇 (上)

《XDC约束技巧之时钟篇》中曾对I/O约束做过简要概括&#xff0c;相比较而言&#xff0c;XDC中的I/O约束虽然形式简单&#xff0c;但整体思路和约束方法却与UCF大相径庭。加之FPGA的应用特性决定了其在接口上有多种构建和实现方式&#xff0c;所以从UCF到XDC的转换过程中&#x…

都别吹牛逼了,2个英语指令简单评测便知ChatGPT、博弈Ai、文心一言、通义千问、讯飞星火真实水平

一、博弈Ai&#xff1a;GPT3.5版 演示&#xff1a;https://chat.bo-e.com/ 1、充当英语发言助手 评分&#xff1a;10分 总结&#xff1a;完整满足了指令需求 2、充当英汉互译器 评分&#xff1a;8分 总结&#xff1a;基本满足了我的指令需求。但是有点啰嗦&#xff0c;扣…

MySQL---视图(定义、修改、更新、重命名、删除)

1. 定义视图 视图&#xff08;view&#xff09;是一个虚拟表&#xff0c;非真实存在&#xff0c;其本质是根据SQL语句获取动态的数据集&#xff0c;并为其命 名&#xff0c;用户使用时只需使用视图名称即可获取结果集&#xff0c;并可以将其当作表来使用。 数据库中只存放了…

SIR模型与R模拟

SIR病毒模型R模拟 文章目录 SIR病毒模型R模拟[toc]1.SIR病毒模型2.R模拟 1.SIR病毒模型 SIR病毒模型的的三个字母分别为病毒传播过程中的三种状态&#xff0c;其中 S&#xff0c;表示易感染者&#xff0c;即没有被感染病毒的人群I&#xff0c;表示已感染者&#xff0c;即被感…

Spring_jdbcTemplate基本使用

文章目录 一、导入spring-jdbc和spring-tx坐标二、创建数据库表和实体在applicationContext.xml中配置连接池和JdbcTemplate在test数据库中创建account表 三、创建JdbcTemplate对象四、执行数据库操作 一、导入spring-jdbc和spring-tx坐标 <dependency><groupId>o…

Vue.js快速入门

文章目录 一、Vue基础1.1 渐进式框架​1.2 第一个Vue程序1.3 el 挂载点1.4 data 数据对象 二、Vue 指令2.1 v-text 文本值2.2 v-html 标签元素2.3 v-on 绑定事件2.4 v-show 隐藏2.5 v-if 消除2.6 v-bind 属性值2.7 v-for 生成列表2.8 v-model 双向数据绑定 三、axios 网络请求库…

EXCEL数组公式的理解和技巧(未完成)

1 小心特例 frenquce 会划分为n1个区间 SUMPRODUCT(IF(FREQUENCY(B5:B18,B5:B18)>0,1,0)*IF(VALUE(MID(A5:A18,6,1))5,1,0)) 2 用0/ 和1/0 数组公式来解决问题 SUMPRODUCT(1/COUNTIF(B5:B18,B5:B18)) 这个只返回了B列里不重复的数据个数&#xff0c;确实是10个 SUMPRODU…

在 Windows 上安装 kind

一、前言 个人主页: ζ小菜鸡大家好我是ζ小菜鸡&#xff0c;让我们一起学习在 Windows 上安装 kind。如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连) 二、 kind是什么 kind 是一个使用 Docker 容器“节点”运行 Kubernetes 集群的工具。使用 kind 工具搭建的 Kubernetes…

【Linux Network】应用层协议——HTTP

目录 1. 认识URL 2. urlencode和urldecode urlencode例子&#xff1a; urldecode例子&#xff1a; 3. HTTP协议格式 3.1 HTTP请求&#xff1a; 3.2 HTTP响应&#xff1a; 3.3 HTTP的方法&#xff1a; 3.4 GET方法和POST方法的区别 3.5 HTTP的状态码&#xff1a; 3.6 HTTP常见He…

Python学习20:温度转换 II(python123)

温度的刻画有两个不同体系&#xff1a;摄氏度&#xff08;Celsius&#xff09;和华氏度&#xff08;Fabrenheit&#xff09;。‪‪‪‪‪‪‫‪‪‪‪‪‪‪‪‪‪‪‫‪‪‪‪‪‫‪‪‪‪‪‫‪‪‪‪‪‪‫‪‪‪‪‪‫‪ 请编写程序将用户输入华氏度转换为摄氏度&#xff…