开发者社区> double2li> 正文

漫话C++0x(五)—- thread, mutex, condition_variable

简介: 熟悉C++98的朋友,应该都知道,在C++98中没有thread, mutex, condition_variable这些与concurrency相关的特性支持,如果需要写多线程相关程序,都要借助于不同平台上各自提供的api,这样带来的问题就是程序的跨平台移植性比较差,经常要用一大堆的#ifdef WIN32类似的宏来区分不同的平台,搞得程序很难看。
+关注继续查看

 熟悉C++98的朋友,应该都知道,在C++98中没有thread, mutex, condition_variable这些与concurrency相关的特性支持,如果需要写多线程相关程序,都要借助于不同平台上各自提供的api,这样带来的问题就是程序的跨平台移植性比较差,经常要用一大堆的#ifdef WIN32类似的宏来区分不同的平台,搞得程序很难看。C++0x最原始的初衷之一就是为了让C++的功能更加强大,更加方便使用。现如今硬件如此发达,concurrency在程序设计中已经是司空见惯的事情了,如果C++再不支持这些concurrency相关的特性,就真的out了。现在,C++程序员的福音到了,C++0x提供了对thread, mutex, condition_variable这些concurrency相关特性的支持,以后多线程这一块的代码可以完全跨平台了,而且由于C++0x封装的都比较好,代码写起来也十分简洁。下面开始介绍今天的内容。

    • 1. thread

    写过多线程程序的朋友,相信对thread本身都不会陌生,这里不对thread本身做太多的说明,以介绍C++0x中提供的thread的用法为主。请大家先看下面的例子:

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
#include < iostream>
#include < string>
#include < thread>
 
class Printer
{
public:
    void Print(int id, std::string& name)
    {   
        std::cout < < "id=" << id << ", name=" << name;
    }   
};
 
void Hello()
{
    std::cout << "hello world" << std::endl;
}
 
int main()
{
    Printer p;
    int id = 1;
    std::string name("xiao5ge");
 
    std::thread t1(&Printer::Print, p, id, name);
    std::thread t2(std::bind(&Printer::Print, p, id, name));
    std::thread t3([&]{ p.Print(id, name); }); 
    std::thread t4(Hello);
 
    t4.join();
    t3.join();
    t2.join();
    t1.join();
}

    下面我们来通过分析上面的例子,来说明一下thread的用法。上面的t1-t4的四个例子,分别是thread的四种构造方式,我们一一来介绍一下:

      • (1)这种方式是通过变参数模板实现的,第一个参数是线程入口函数的地址,后面的参数按函数调用时的参数顺序传入。这里需要说明两点:一是变参模板也是C++0x新增的特性,将会在后面的文章中介绍;二是,类成员函数的第一个参数永远是this,所以这里第一个参数放的是对象本身。
      • (2)这种方式是传入一个std::function对象,bind/function在前一篇中有介绍,不熟悉的朋友可以先看一下C++0x系列的第四篇。
      • (3)这种方式是传入一个lambda表达式,也即一个closure,是比较常用的方式。关于lambda也在上一篇中有介绍。
      • (4)这种方式是最简单,最常用的方式,直接传入一个函数,写过多线程程序的朋友应该对这种方式最为熟悉。

    上面介绍了C++0x thread的基本用法,下面需要再补充几点使用过程需要注意的事项:

      • (1)如果入口函数的参数是以引用或指针形式传入的,需要使用者保证在线程运行过程中,这些参数一直是有效的。同时,如果有多个线程会访问或者修改这些变量,需要使用者做好同步,保证一致性。
      • (2)关于上面提到的几种构造方式,简单的直接用4,复杂的推荐的选择顺序:1->3->2,即变参模板方式->lambda方式->bind方式
      • (3)通常需要等子线程运行完,主线程才退出,所以在主线程中通常需要调各子线程的join()。
    • 2. mutex

    mutex实现的是“互斥锁”的语义,在多线程的程序中,经常需要通过锁的机制来保证数据的一致性,C++0x提供了下面四种语义的mutex:

      • (1) std::mutex: 普通的互斥锁,不能递归使用
      • (2) std::timed_mutex:带超时的互斥锁,不能递归使用
      • (3) std::recursive_mutex:递归互斥锁
      • (3) std::recursive_timed_mutex:带超时的递归互斥锁

    关于mutex的使用,我们通常建议使用RAII(Resource Acquisition is Initialization)的方式,即在构造的时候lock, 析构的时候unlock, 不建议直接显式的lock/unlock,因为这样比较容易出错。因此,C++0x也提供了两个工具类std::lock_guard和std::unique_lock来辅助我们使用mutex,下面我们通过例子来看一下具体的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include < mutex>
 
// global vars
int data = 0;
std::mutex data_mutex;
 
// thread 1
{
    std::lock_guard< std::mutex> locker(data_mutex);
    data = 1;
}
 
// thread 2
{
    std::lock_guard< std::mutex> locker(data_mutex);
    data = 2;
}

    从上面的例子,相信大家可以对mutex的基本使用方法都应该比较清楚了,由于mutex本身就比较简单,这里不再赘言。说一下std::unique_lock和std::lock_guard的区别,std::lock_guard只允许RAII方式的使用,而std::unique_lock可以在构造之后调用lock/unlock, 更加灵活一些,但使用的时候出错的机率也更大一些,所以如果没有什么特殊的需求,通常推荐尽量使用std::lock_guard.

    • 3. condition_variable

    关于condition_variable,它的语义是今天讲的三个内容里面相对复杂一些的,我在之前也写过一篇关于它的文章,不熟悉的朋友可以先阅读一下《条件变量(Condition Variable)详解》这篇文章,先了解一下条件变量,以方便理解后面的内容。我们知道,条件变量主要是用在多线程之间修改了shared_data之后的相互通信,由于条件变量在多线程编程中非常有用,所以C++0x也添加了对条件变量的支持,下面是C++0x提供的两种不同类型的条件变量:

  • (1)condition_variable: 用在std::unique_lock< std::mutex>上wait, 比较高效。
  • (2)condition_variable_any: 可以用在任意mutex上wait, 比较灵活,但效率比condition_variable差一些。

    下面我们通过例子来看看,条件变量在C++0x中的使用方式:

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
// global
std::atomic< bool> is_finish(false);
std::mutex finish_mutex;
std::condition_variable finish_cond;
 
// thread 1
{
    std::unique< std::mutex> locker(finish_mutex);
 
    // 1. loop wait
    while (!is_finish)
    {   
        finish_cond.wait(locker);
    }   
 
    // 2. wait until prediction is true, loop inside
    finish_cond.wait(locker, []{ return is_finish; }); 
 
    // 3. wait until eithor prediction is true or timeout
    finish_cond.wait(locker, std::chrono::seconds(1),
            []{ return is_finish; }); 
}
 
// thread 2
{
    is_finish = true;
 
    // 1. notify one of the waiter
    finish_cond.notify_one();
 
    // 2. notify all the waiter
    finish_cond.notify_all();
}

    上面的例子,基本覆盖了C++0x提供的条件变量的主要用法。下面我们来一一分析一下,帮助大家更好的理解:

  • (1)关于wait,有三种基本的用法:第1种是在指定的条件上循环等待,直到条件为真notify时才会继续执行后面的逻辑;第2种用法语义上和第1种是一样的,但是不是用户做显式的loop等待,用户传入一个需要满足的条件的closure, wait一直等到这个条件为真被notify时才会返回继续执行下面的逻辑,可以理解为这时候,在wait内部有一个loop;第3 种用法,加了一个超时的语义,wait一直等到条件为真或者超时,被notify时才会返回继续执行下面的逻辑。
  • (2)关于notify, 有两种:第1种是notify_one, 只唤醒一个在wait的线程; 第2种是notify_all,唤醒所有在wait的线程,相当于一个broadcast的语义。

    以上即是今天的主要内容,希望对正在学习C++0x的朋友有所帮助,荣幸之至!

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
【C++0x】表达式之类型(decltype)
C++0x引入了新的关键字decltype,它是一个操作符,用来取得表达式的类型,主要在泛型编程中使用。这里,简单介绍一下语法规则。语法形式:decltype (expression)其中,这里的括号必不可少(这点不同于sizeof操作符)。
877 0
C++0x新特性
   1、auto keyword 自动变量类型推断    auto iNum = 0;    iNum推断为int型    2、decltype声明变量类型    int nVariable1;    decltype(nViarable1)<==>int auto xVariable = ReturnXFun(); 想再获得ReturnXFun的返回值类型,但
938 0
ISO C++委员会批准C++0x最终草案
【简讯】IS0 C++委员会正式批准了C++编程语言国际标准最终草案(FDIS)。 标准本身已经完成,接下来将是根据委员会会议修改意见更新工作草案,预计将用三周时间完成FDIS草案,然后交给日内瓦的ITTF,最新的C++标准将在夏天发布,先前被临时命名为C++0x的新标准将被称为C++ 2011。
782 0
C++ STL学习之【vector的使用】
vector 是表示可变大小数组的序列 容器,其使用的是一块 连续 的空间,因为是动态增长的数组,所以 vector 在空间不够时会扩容;vector 优点之一是支持 下标的随机访问,缺点也很明显,头插或中部插入效率很低,这和我们之前学过的 顺序表 性质很像,不过在结构设计上,两者是截然不同的
30 0
C++ STL学习之【string类的模拟实现】
string 本质上就是一个专注于存储字符的顺序表,使用起来很方便;但在模拟实现 string 时,有许多值得注意的点,下面就来看看 string 类是如何诞生的吧
46 0
C++ STL 学习之【string】
STL 是 C++ 的重要组成部分,由六大部分构成:伪函数、空间配置器、算法、容器、迭代器 和 配接器,其中各种各样的 容器 可以很好的辅助我们写程序,比如今天要介绍的 string,有了它之后,我们对字符串的操作就能变得行云流水
40 0
【查找算法】解析学习四大常用的计算机查找算法 | C++
在数据处理的过程中,能否在最短时间内去找到目的数据,是编程开发人员非常值得关心的一个问题。所谓查找,也被称为搜索,它是指从数据文件中找出满足某些条件的记录。在数据结构中描述算法时习惯用“查找”,而在搜索引擎中找信息或资料时习惯用“搜索”。我们在电话簿中查找某人的电话号码,电话簿就像是数据文件库,而姓名就是去查找电话号码的键值。我们经常使用的搜索引擎所设计的Spider程序(网页抓取程序爬虫)会主动经由网站上的超链接“爬行”到另一个网站,搜集每个网站上的信息并且收录到数据库中,这其中就涉及到了今天要讲的查找算法。
22 0
【奇妙的数据结构世界】用图像和代码对堆栈的使用进行透彻学习 | C++
简单来说,数据结构是一种辅助程序设计并且进行优化的方法论,它不仅讨论数据的存储与处理的方法,同时也考虑到了数据彼此之间的关系与运算,从而极大程度的提高程序执行的效率,减少对内存空间的占用等。不同种类的数据结构适用于不同的程序应用,选择合适正确的数据结构,可以让算法发挥出更大的性能,给设计的程序带来更高效率的算法。
20 0
+关注
double2li
一个在IT行业摸爬滚打的老司机
文章
问答
文章排行榜
最热
最新
相关电子书
更多
GPON Class C++ SFP O;T Transce
立即下载
GPON Class C++ SFP OLT Transce
立即下载
继承与功能组合
立即下载