简单说一下C++11的并发

本文介绍了C++11中处理并发的关键概念,包括volatile的作用、Mutex和Lock的使用、只调用一次的解决方案、Condition Variable的示例以及atomic类型的详细说明,强调了C++11在并发编程中的新特性及其优势。
摘要由CSDN通过智能技术生成

C++11中定义的data race是“不同线程中的两个互相冲突的动作,其中至少有一个不是atomic的,而且无一个动作发生在另一个动作之前”。data race总会导致不可预期的行为。在C++11以前,并不能保证“不同的对象拥有各自的内存区”,也就是,在C++98/C++03这个标准是针对单线程的进程的标准,严格来讲,从C++11之前,并行处理不同的对象也可能会导致不可预期的行为。这里只是谈C++11以后的并发。并发往往是针对相同数据,这里的相同数据是说,使用相同的内存区的数据,从C++11开始,一般变量都能保证拥有自己的内存区,但是bitfield例外,不同的bitfield有可能共享同一块内存区。

volatile

与java不同:
- volatile在C++中是一个关键字,主要用来阻止“过度优化”
- 在java中,volatile提供了某系atomicity和order的一些保证

在C++中,volatile只保证对变量的读取的load内存的操作不会被编译器优化,其他的优化几乎没有,并没有像java中提供类似于atomicity和order的保证。

Mutex和Lock

处理并发通常用到的就是锁和互斥量,c++11的标准中提供了需要的互斥量,这里简单演示一下用法,具体的内容后续再说:
本次主要是讨论关于互斥量的使用。首先看一个建的例子:

#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
using namespace std;
int val=20;
std::mutex valMutex;            //control exclusive access to val
int func(int argc){
    //thread 1 add
    valMutex.lock();
    val += 2;
    cout << "func : " << val << endl;
    valMutex.unlock();
    return 0;
}
int foo(int argc){
    //thread 2 
    valMutex.lock();
    val *= 2;
    cout << "foo : " << val << endl;
    valMutex.unlock();
    return 0;
}
int main()
{
    val = 10;
    valMutex.lock();            //保证thread 是在线程1、2前输出
    thread t1(func, 0);
    thread t2(foo, 0);

    cout << "thread:" << endl;
    valMutex.unlock();
    t1.join();
    t2.join();
    system("pause");
    return 0;
}

上面的代码在10行的位置声明看一个互斥量valMutex,并通过该互斥量实现读写的互斥,但是上面的做法有很多的隐患,我们如果程序在加锁以后抛出了异常,那么这个锁有可能永远无法被获取,标准中提供了一种利用代码域的概念使用局部变量的形式进行加锁和解锁操作。
但是并不推荐这么使用,这么使用不符合RAII的规范,建议使用基于RAII规范,你会发现C++11为你提供了这一个规范,我们不妨使用lock_guard这个模板,如下:

// Concurr001.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <chrono>
#include <thread>
#include <future>
#include <atomic>
#include <mutex>
#include <condition_variable>
using namespace std;
std::mutex sMutex;
int solve()
{

    {
        std::lock_guard<mutex> lg(sMutex);
        /*
        需要互斥的代码
        */
    }//释放锁
}

上面的程序利用了RAII的机制(关于RAII机制的问题,我们通过一个专门的地方进行详细的分析,其实是个很有用,很有趣的东西),避免了因为异常跳出函数而不能或者忘记释放锁带来的问题。这种思想同样也应用在了智能指针中,RAII的使用,确实很有趣。
其实,我们去看这些源码,甚至我们可以想象在其中干了些什么,mutex本身有四种可供选择:
- mutex
- recursove_mutex
- timed_mutex
- recursive_timed_mutex

对应的lock的操作也有四种:
- lock() : 捕获mutex,否则阻塞
- try_lock() : 捕获mutex,不成功返回false
- unlock(): 释放锁定的互斥量
- try_lock_for() : (适用于timed_mutexrecursive_timed_mutex)试着在时间段内捕获一个lock
- try_lock_until():(适用于timed_mutexrecursive_timed_mutex)试着捕获一个lock知道某个时间点

  • recursove_mutexrecursive_timed_mutex是支持多个lock的,其余的不行。
  • class std::timed_mutex额外允许你传递一个时间段或者时间点,用来定义多长时间内他可以尝试捕获一个lock,为它提供了try_lock_for()try_lock_until()
  • recursive_timed_mutex允许一个线程多次取得其lock

我们并不会经常使用mutex的成员函数中的这些操作,如果上面提到了,我们经常会用一点小技巧,使用RAII来完成。C++11为此提供了:
- lock_guard
- unique_lock

lock_guard提供了如下的操作:

操作 效果
lock_guard lg(m) 为mutex m建立一个lock guard并锁定之
lock_guard lg(m, adopt_lock) 为已经被锁定的mutex m见了一个 lock guard
lg.~lock_guard() 解锁mutex并销毁lock guard

unique_lock的操作略多一些

操作 效果
unique_lock l default构造函数,建立一个lock但不关联任何mutex
unique_lock l(m) 为mutex m 建立一个lock guard并锁定它
unique_lock l(m,adopt_lock) 为已锁定的mutex m建立一个lock guard
unique_lock l(m,defer_lock) 为mutex m建立一个lock guard,但不锁定它
unique_lock l(m,try_lock) 为mutex m建立一个lock guard, 并试图锁定它
unique_lock l(m,dur) 为mutex m建立一个lock guard,并试图在时间段dur内锁定它
unique_lock l(m,tp) 为mutex m建立一个lock guard,并试图锁定直到时间点tp
unique_lock l(rv) move构造函数
if(l) 检查关联的mutex是否被锁定
l.mutex() 返回一个指针指向关联的mutex
l.lock() 锁定
l.try_lock() 尝试锁定,如果成功返回true
l.try_lock_for(dur) 尝试在时间段dur内锁定关联的mutex,如果成功
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值