C++11有关线程同步的使用

c++11 使用detach()时,主线程和孤儿detach线程的同步控制 在c++ 中,程序默认是只有一个Main Thread运行的,其入口函数是主函数main(),其中若开辟了其他的新的子线程,对这些子线程的处理方式有两种, 一种是Join(),即main thread在被join的子线程没执行完的这段时间里,什么也不做,直到子线程执行完毕,才会继续向下执行 另一种是Detach(),如果没有其他一些同步措施的话,此时 子线程和main thread 完全分离,... 阅读详情

C++11有关线程同步的使用

 

本文链接:https://blog.youkuaiyun.com/fengxinlinux/article/details/76686829

互斥量和条件变量是控制线程同步的常用手段,用来保护多线程同时访问的共享数据。
c++11提供了这些操作,同时还提供了原子变量和一次调用的操作,用起来非常的方便。
我们在这里只介绍如何在C++中使用这些同步机制,有关概念的介绍我们就不在这里多说了。


互斥量

C++11中提供了如下4种语义的互斥量(mutex):

  • std::mutex:独占的互斥量,不能递归使用。
  • std::timed_mutex:带超时的独占互斥量,不能递归使用。
  • std::recursive_mutex:递归互斥量,不带超时功能。
  • std::recursive_timed_mutex:带超时的递归互斥量。

独占互斥量std::mutex

这些互斥量的基本接口很相似,一般用法是通过lock()方法来阻塞线程,直到获得互斥量的所有权为止。在线程获得互斥量并完成任务之后,就必须使用unlock()来解除对互斥量的占用,lock()和unlock()必须成对出现。try_lock()尝试锁定互斥量,如果成功则返回true,如果失败则返回false,它是非阻塞的。
std::mutex的基本用法代码如下:

#include<iostream>
#include<thread>
#include<mutex>
#include<chrono>
using namespace std;


std::mutex g_lock;

void func()
{
    g_lock.lock();

    std::cout<<"entered thread "<<std::this_thread::get_id()<<std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout<<"leaving thread "<<std::this_thread::get_id()<<std::endl;

    g_lock.unlock();
}  

int main()
{
    std::thread t1(func);
    std::thread t2(func);
    std::thread t3(func);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

使用lock_guard可以简化lock/unlock的写法,同时也更安全,因为lock_guard在构造时会自动锁定互斥量,而在退出作用域后进行析构时就会自动解锁,从而保证了互斥量的正确操作,避免忘记unlock操作,因此,应尽量用lokc_guard。lock_guard用到了RAII技术,这种技术在类的构造函数中分配资源,在析构函数中释放资源,保证资源在出了作用域之后就释放。上面的例子使用lock_guard后会更简洁,代码如下:

void func()
{
    lock_guard<std::mutex> locker(g_lock);   //出作用域之后自动解锁
    cout<<"entered thread "<<this_thread::get_id()<<endl;
    this_thread::sleep_for(std::chrono::seconds(1));
    cout<<"leaving thread "<<std::this_thread::get_id()<<endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

递归的独占互斥量std::recursive_mutex

递归锁允许同一线程多次获得该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。一个线程多次获取同一个互斥量时会发生死锁。要解决这个死锁的问题,一个简单的办法就是用递归锁:std::recursive_mutex,它允许同一线程多次获得互斥量。
代码示例:

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;


struct Complex
{
    std::recursive_mutex mutex;
    int i;

    Complex():i(0){}

    void mul(int x)
    {
        std::lock_guard<std::recursive_mutex> lock(mutex);
        i*=x;
    }
    void div(int x)
    {
        std::lock_guard<std::recursive_mutex> lock(mutex);
        i/=x;

    }
    void both(int x,int y)
    {
        std::lock_guard<std::recursive_mutex> lock(mutex);
        mul(x);
        div(y);
    }
};

int main()
{
    Complex complex;
    complex.both(32,23);  //因为同一线程可以多次获取同一互斥量,不会发生死锁

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

需要注意的是尽量不要使用递归锁好,主要原因如下:

  • 需要用到递归锁定的多线程互斥处理往往本身就是可以简化的,允许递归互斥很容易放纵复杂逻辑产生,从而导致一些多线程同步引起的晦涩问题。
  • 递归锁比起非递归锁,效率会低一些。
  • 递归锁虽然允许同一个线程多次获得同一互斥量,可重复获得的最大次数并未具体说明,一旦超过一定次数,再对lock进行调用就会抛出std::system错误。

带超时的互斥量

std::timed_mutex是超时的独占锁,std::recursive_timed_mutex是超时的递归锁,主要用在获取锁时超时等待功能,因为有时不知道获取锁需要多久,为了不至于一直在等待获取互斥量,就设置一个等待超时时间,在超时后还可以做其他事情。
std::timed_mutex比std::mutex多了两个超时获取锁的接口:try_lock_for和try_lock_until,这两个接口是用来设置获取互斥量的超时时间。
std::timed_mutex的基本用法如下代码:


#include<iostream>
#include<thread>
#include<mutex>
using namespace std;

std::timed_mutex mutex1;

void work()
{
    chrono::milliseconds timeout(100);

    while(true)
    {
        if(mutex1.try_lock_for(timeout))
        {
            cout<<this_thread::get_id()<<":do work with the mutex"<<endl;
            chrono::milliseconds sleepDuration(250);
            this_thread::sleep_for(sleepDuration);

            mutex1.unlock();
            this_thread::sleep_for(sleepDuration);

        }
        else
        {
            cout<<this_thread::get_id()<<": do work without mutex"<<endl;

            chrono::milliseconds sleepDuration(100);
            this_thread::sleep_for(sleepDuration);
        }
    }
}


int main()
{
    thread t1(work);
    thread t2(work);

    t1.join();
    t2.join();

    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

在上面的例子中,通过一个while循环不断地去获取超时锁,如果超时还没有获取到锁时就休眠100毫秒,再继续获取超时锁。
相比std::timed_mutex,std::recursive_timed_mutex多了递归锁的功能,允许同一线程多次获得互斥量。

 

条件变量

条件变量是C++11提供的另外一种用于等待的同步机制,它能阻塞一个或多个线程,直到收到另外一个线程发出的通知或者超时,才会唤醒当前阻塞的线程。条件变量需要和互斥量配合起来用。C++11提供了两种条件变量:

  • condition_variable,配合std::unique_lock<std::mutex>进行wait操作。
  • condition_variable_any,和任意带有lock、unlock语义的mutex搭配使用,比较灵活,但效率比condition_variable差一些。

可以看到condition_variable_any比condition_variable更灵活,因为它更通用,对所有的锁都适用,而condition_variable性能更好。

条件变量的使用过程如下:

  • 拥有条件变量的线程获取互斥量。
  • 循环检查某个条件,如果条件不满足,则阻塞直到条件满足;如果条件满足,则向下执行。
  • 某个线程满足条件执行完之后调用notify_one或notify_all唤醒一个或者所有的等待线程。

我们可以看一个经典的生产者-消费者的例子:

#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<unistd.h>
using namespace std;


mutex m_mutex;   //互斥量
int s=0;         //共享资源
condition_variable m_notempty;   //条件变量

void producer()     //生产者
{
    while(1)
    {
        sleep(1);
        m_mutex.lock();  //上锁
        s++;
        cout<<"increase one ,s="<<s<<endl;
        m_mutex.unlock();
        m_notempty.notify_one();
    }
}


void consumer()     //消费者
{
    while(1)
    { 
        sleep(1);
        unique_lock<mutex> locker(m_mutex);  
        while(s==0)
        m_notempty.wait(locker);      //使用条件变量
        s--;
        cout<<"decrease one,s="<<s<<endl;


    }
}
int main()
{
    //创建线程
    thread thread1(producer);
    thread thread2(consumer);


    thread1.join();
    thread2.join();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

上面的例子中,while(s==0) m_notempty.wait(locker); ,这句代码的意思的是当s等于0的时候,阻塞直到条件满足时被唤醒。
我们也可以这么用, m_notempty.wait(locker,[]{return s>0;}); ,将判断条件放到函数里面,意思是wait将一直阻塞,知道判断条件满足时,被唤醒。

 

原子变量

C++11提供了一个原子类型std::atomic<T>,可以使用任意类型作为模板参数,C++11内置了整型的原子变量,可以更方便地使用原子变量,使用原子变量就不需要使用互斥量来保护该变量了,因为对该变量的操作保证其是原子的,是不可中断的。用起来更简洁。
要做一个计时器,使用mutex时,代码如下:


#include<iostream>
#include<mutex>
using namespace std;



struct Counter
{
    public:
    int value;
    std::mutex m_mutex;

    void increment()
    {
        std::lock_guard<std::mutex> lock(mutex);
        value++;
    }

    void decrement()
    {
        std::lock_guard<std::mutex> lock(mutex);
        value--;
    }

    int get()
    {
        return value;
    }
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

如果使用原子变量,就不需要再定义互斥量了,使用更简便。

#include<iostream>
#include<mutex>
using namespace std;



struct Counter
{
    public:

    std::atomic<int> value;

    void increment()
    {
        value++;
    }

    void decrement()
    {
        value--;
    }

    int get()
    {
        return value;
    }
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

 

call_one/once_flag的使用

为了保证在多线程环境中某个函数仅被调用一次,比如,需要初始化某个对象,而这个对象只能初始化一次时,就可以用std::call_once来保证函数在多线程环境中只被调用一次。使用std::call_once时,需要一个once_flag作为call_one的入参,它的用法比较简单。

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;


std::once_flag flag;

void do_once()
{
    std::call_once(flag,[]{std::cout<<"Called once"<<endl;});
}


int main()
{
    std::thread t1(do_once);
    std::thread t2(do_once);
    std::thread t3(do_once);

    t1.join();
    t2.join();
    t3.join();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

运行结果:

Called once

c++11多线程编程同步——使用条件变量condition variable 简述 在多线程编程中,当多个线程之间需要进行某些同步机制时,如某个线程的执行需要另一个线程完成后才能进行,可以使用条件变量。 c++11提供的 condition_variable 类是一个同步原语,它能够阻塞一个或者多个线程,直到另一线程修改共享变量并通知 condition_variable。 也可以把它理解为信号通知机制,一个线程负责发送信号,其他线程等待该信号的触发。 condition... 阅读详情

相关推荐

elasticsearch报错12,000 milliseconds timeout on connection http-outgoing-10 [ACTIVE]

一、问题 项目启动后,获取 elasticsearch 索引报错:12,000 milliseconds timeout on connection http-outgoing-10 [ACTIVE]: org.elasticsearch.ElasticsearchException: 获取索引 {xxx} 是否存在失败:12,000 milliseconds timeout on connection http-outgoing-10 [ACTIVE] at xxx.xxx.elasticsearch.

邓邓子的博客 5486

C#多线程——Thread睡眠、中断、合并、暂停与恢复、终止

C#多线程的一些操作,Sleep、Join、Interupt、暂停与恢复(重点)、终止

AronZhang的博客 3万+

华为云Stack应急预案实战:从IaaS到PaaS的故障场景与恢复指南

本文详细解析华为云Stack应急预案在IaaS到PaaS层的故障场景与恢复实践,涵盖计算节点故障、存储链路中断等典型问题。通过标准化操作流程和实战案例,帮助企业在突发故障时快速恢复业务,确保高可用性。华为云Stack的应急预案体系经过金融、政务等严苛场景验证,恢复时间指标(RTO)控制在30分钟以内。

weixin_33724059的博客 373

C# 延迟Task.Delay()和Thread.Sleep() 学习

Task.Delay,Thread.Sleep 的共性跟异性 Thread.Sleep 方法 将当前线程挂起指定的时间。 Sleep(Int32) public static void Sleep (int millisecondsTimeout); 参数 millisecondsTimeout Int32 挂起线程的毫秒数。 如果 millisecondsTimeout 参数的值为零,则该线程会将其时间片的剩余部分让给任何已经准备好运行的、具有同等优先级的线程。 如果没有其他已经准备好运行的、具有同等

热爱编程的男孩子 1万+

clang-tidy 使用指南

clang-tidy 需要一个编译数据库来了解如何编译你的代码。编译数据库通常是一个名为 compile_commands.json 的文件。在这个示例中,clang-tidy 会对 test.cpp 文件进行分析,并报告任何潜在的问题。例如,它可能会警告 test 函数中的未使用变量 x。clang-tidy 是一个用于 C++ 代码的静态分析工具,可以帮助你发现代码中的潜在问题和改进点。这将在 build 目录中生成一个 compile_commands.json 文件。

weixin_42244181的博客 2362

从零到一:基于Esp32与LVGL的ST7789屏幕驱动全流程实战

本文详细介绍了基于Esp32与LVGL的ST7789屏幕驱动全流程实战,包括环境准备、驱动文件改造、硬件接口配置、LVGL集成与优化等关键步骤。通过VSCode+ESP-IDF开发环境,读者可以快速掌握SPI模式下的屏幕驱动开发,解决常见问题如颜色错乱、画面撕裂等,并实现LVGL的高效集成与性能优化。

weixin_33725722的博客 711

es如何解决timeout超时30秒问题

es如何解决timeout超时30秒问题; 30,000 milliseconds timeout on connection http-outgoing-0

qq_35515283的博客 2万+

matianl:SAP 最详细的权限管理解析

上文所说的系统自带权限对象与权限字段仅能满足有限的需要,其权限审核的逻辑也是系统硬编码了的,我们能做的只是是否启用某项权限对象的检查(使用SU22)。如果需要自定义,通过SU20、SU21定义即可。

matinal 當冬夜漸暖 。公众号:matinal 2155

C++11 多线程的使用及线程同步

C++线程操作 C++11 中提供的线程类 std::thread ,基于此类创建一个新的线程相对简单,只需要提供线程函数和线程对象即可; 文章目录C++线程操作1.命名空间 this_thread1.1 get_id()1.2 sleep_for()1.3 sleep_until1.4 yield()2. C++ 线程类2.1构造函数2.2 公共成员函数2.2.1 get_id() 函数2.2.2 join() 函数2.2.3 detach() 函数2.2.4 joinable() 函数2.3 静态

weixin_49730048的博客 2777

标准恢复二极管整流器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告

报告页数: 150 图表数: 100 报告价格:¥16800 本文研究全球与中国市场标准恢复二极管整流器的发展现状及未来发展趋势,分别从生产和消费的角度分析标准恢复二极管整流器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。 主要生产商包括: Yangzhou Yangjie Vishay Intertechnology Power Semiconductors Inc Nexperia B

Fj69258的博客 5849

dpdk 源码分析 rte_ring

rte_ring源码分析

qq_40843445的博客 929

c++11多线程编程同步——使用future和promise

简介 在多线程编程中,需要数据共享,如一个线程需要使用另一线程运算产生的数据。 涉及到异步编程时,有时需要线程间同步,如执行异步订阅消息时,当订阅消息的结果返回成功后,处理消息的线程才需要运行。 以上场景的都可以使用std::future和std::promise实现。 std::promise是可以存储类型T的值的对象,该值可以被另一线程的std::future对象获取,并提供了同步机制。 ...

guotianqing的博客 5726

PointNet++改进策略目录

后续我将如何使用文章中创新点加入的PointNet++中代码实现部分进行更新。

u012901740的博客 2196

C++11使用mutex和condition_variable实现线程同步

C++中A、B、C三个线程模拟购买100张车票,A输出99,B输出98,C输出97,然后又循环A输出96,B95,C94,直到0,使用线程同步,如何实现?这是一种按顺序执行线程的问题,应该实现?

weixin_45144862的博客 409

深入探究ASP.NET Core读取Request.Body的正确方式

前言# 相信大家在使用ASP.NET Core进行开发的时候,肯定会涉及到读取Request.Body的场景,毕竟我们大部分的POST请求都是将数据存放到Http的Body当中。因为笔者日常开发所使用的主要也是ASP.NET Core所以笔者也遇到这这种场景,关于本篇文章所套路的内容,来自于在开发过程中我遇到的关于Request.Body的读取问题。在之前的使用的时候,基本上都是借助搜索引擎搜索的答案,并没有太关注这个,发现自己理解的和正确的使用之间存在很大的误区。故有感而发,便写下此文,以作记录。学无止境

萌兔兔MMQ!! 777

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

c++11 线程同步与互斥如何使用

当一个线程需要访问共享资源时,它必须首先获取互斥量的锁,以确保其他线程无法同时访问该资源。当该线程完成它的工作后,必须释放互斥量的锁,以允许其他线程访问该共享资源。当多个线程需要访问同一共享资源时,需要采用互斥量来保证在任一时刻只有一个线程能访问该资源,从而防止数据竞争和内存泄漏的问题。原子操作主要用于保证对特定数据类型的操作在多线程环境下是原子性的,即不会被中断。在 C++11 中,线程同步和互斥可以使用互斥量、条件变量以及原子操作来实现。来通知等待条件变量的线程开始执行。变量被用作互斥量和条件变量,

开挂的熊猫 1819

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

使用C++11实现一个半同步半异步线程池

http://www.cnblogs.com/highway-9/p/5988991.html 前言 C++11之前我们使用线程需要系统提供API、posix线程库或者使用boost提供的线程库,C++11后就加入了跨平台的线程类std::thread,线程同步相关类std::mutex、std::lock_guard、std::condition_variable、std::at

zdy0_2004的专栏 586

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

【RabbitMQ 项目】前置技术:含同步操作的线程池——C++11<future>使用

C++11同步线程池

weixin_74113106的博客 1529

c++11使用atomic多线程同步3种方法

// 使用CAS实现多线程无锁访问共享变量,达到线程同步 # include <iostream> # include <thread> # include <mutex> # include <atomic> using namespace std; std::atomic<int> var(0); void add() { ...

少言才不会咸's Tech-blog 2071

c++11 线程同步信号的使用

信号量的作用:一个线程等在哪里,其他的线程发出通知让等待的线程继续运行。 线程A std::mutex m; std::condition_variable c; void A_proc() { std::unique_lock lk(m); //进入临界区 c.wait(m); //A线程等待直到通知到来 } void B_proc() { std::uni

john_crash的专栏 729

使用C++11开发一个半同步半异步线程池

使用C++11开发一个半同步半异步线程池

qq_25048473的博客 308

C++11使用std::future和std::promise实现线程同步和异步通信

std::future:用于获取异步操作的结果,可以阻塞直到结果可用(eg:std::future.get()方法)。std::promise:用于设置异步操作的结果,通常与 std::future 一起使用。std::async:提供了一种简单的方式来启动异步任务并返回一个 std::future,用于获取任务结果。通过使用 std::future 和 std::promise,可以方便地在不同线程之间进行同步和数据传递,简化了异步编程的复杂性。

prerfect_cat的博客 805

C++ 11中使用条件变量和锁机制同步线程

摘自:C++ Concurrency in Action 使用条件变量进行线程同步 ​ #include <thread> #include <mutex> #include <iostream> #include <chrono> #include <condition_variable> #include <queue&...

OpticalSoliton的专栏 317
上一篇: 函数栈&EIP、EBP、ESP寄存器的作用
下一篇: fcntl
TuxedoLinux
博客等级 码龄9年 268粉丝 34原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值