c++11中最重要的特性之一就是对多线程的支持了,然而《c++ primer》5th却没有这部分内容的介绍,着实人有点遗憾。在网上了解到了一些关于thread库的内容。这是几个比较不错的学习thread库的资源:

Thread support library                    : http://en.cppreference.com/w/cpp/thread

Cpp reference/thread                      : http://www.cplusplus.com/reference/thread/

<< C++ Concurrency In Action >>  :http://files.cnblogs.com/files/ittinybird/CplusplusConcurrencyInAction.pdf             //Recommend!

前两个网站我还是非常喜欢的,都是在线的帮助手册,两个选择其中一个就可以了,看你口味选择就好了。最后一个是原版的《C++ Concurrency In Action》,非常棒的一本书,鉴于中文译版已经被黑出翔了,所以能看英文就看英文版吧,我也是硬着头皮啃了一点儿。以下是我学习thread库的一点感受和见解,如果你有发现我的错误,还请你及时批评指正,我将万分感谢

有关线程、并发相关的基础知识,我就不浪费篇幅了。

一个简单的使用线程的Demo

c++11提供了一个新的头文件<thread>提供了对线程函数的支持的声明(其他数据保护相关的声明放在其他的头文件中,暂时先从thread头文件入手吧),写一个多线程的程序需要引用这个新的头文件:

#include <iostream>
#include <thread> void fun()
{
std::cout << "A new thread!" << std::endl;
} int main()
{
std::thread t(fun);
t.join();
std::cout << "Main thread!" << std::endl;
}

  这样的demo就是一个简单的多线程的应用了。其输出如下:

A new thread!
Main thread!

因此我们可以猜测到它的执行流大致是这样的:

那么程序的执行流是如何从main()转去执行fun()的呢,下面我们先看看thread这个类。

线程的启动

我的环境是CentOS7 + g++4.8.3 头文件/usr/include/c++/4.8.3/thread中有thread类的完整声明(我的windows环境是win8.1+vs2013,在默认安装的情况下thread头文件的路径是C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\thread)。代码太长,我就不贴出来了。c++线程库通过构造一个线程对象,来启动一个线程。那么我们就先来看一下thread的构造函数:

以下贴出thread类的代码均是出自GNU的实现版本

class thread
{
...
public:
thread() noexcept = default;
thread(thread&) = delete;
thread(const thread&) = delete;
thread(thread&& __t) noexcept
{ swap(__t); }
template<typename _Callable, typename... _Args>
explicit thread(_Callable&& __f, _Args&&... __args)
{
_M_start_thread(_M_make_routine(std::__bind_simple(
std::forward<_Callable>(__f),
std::forward<_Args>(__args)...)));
}
...
};

这几行代码里边,却有着大量的有关c++11特性的内容,右值引用、noexcept、=delete、以及可调用对象这些不是本篇博客要关注的点,因此就不详细的展开了。我们主要来看这个构造函数模板,

template<typename _Callable, typename... _Args>
explicit thread(_Callable&& __f, _Args&&... __args);

可以清楚的看见它把参数都交给了std::__bind_simple()处理了,而对于std::__bind_simple()的定义在其头文件<functional>中,和std::bind()的用法一样,具体区别建议还是看看头文件比较好,这里我就多个事,顺手就贴出来了:

template<typename _Callable, typename... _Args>
typename _Bind_simple_helper<_Callable, _Args...>::__type
__bind_simple(_Callable&& __callable, _Args&&... __args)
{
typedef _Bind_simple_helper<_Callable, _Args...> __helper_type;
typedef typename __helper_type::__maybe_type __maybe_type;
typedef typename __helper_type::__type __result_type;
return __result_type(
__maybe_type::__do_wrap( std::forward<_Callable>(__callable)),
std::forward<_Args>(__args)...);
} template<typename _Result, typename _Func, typename... _BoundArgs>
inline typename _Bindres_helper<_Result, _Func, _BoundArgs...>::type
bind(_Func&& __f, _BoundArgs&&... __args)
{
typedef _Bindres_helper<_Result, _Func, _BoundArgs...> __helper_type;
typedef typename __helper_type::__maybe_type __maybe_type;
typedef typename __helper_type::type __result_type;
return __result_type(__maybe_type::__do_wrap(std::forward<_Func>(__f)),
std::forward<_BoundArgs>(__args)...);
}

功力有限std::bind()具体实现我就不深究了,有机会我在研究研究。但是不难看出,这两个函数的作用大体上就是封装一个函数及其参数,返回一个__type类。thread在构造一个新的对象时,便是传入了一个__type对象给_M_start_thread()实现启动一个线程的。为什么要这样做呢?我们可以认为这是由于OS的实现(我也是网上听说,如果你知道答案,不妨告诉我),用过Linux上的线程库pthread的应该对pthread_create()中的start_routine参数有印象,它是一个函数指针,其对应的函数原型如下:

void* (*start_routine) (void*);

这样就缩小了两者之间的差异,剩下的事就只要把__type的地址传进去就可以了。由于使用的这样的实现,std::thread()创建一个新的线程可以接受任意的可调用对象类型(带参数或者不带参数),包括lambda表达式(带变量捕获或者不带),函数,函数对象,以及函数指针。

      上面我们写了一个不带参数的demo,现在我们就创建包含参数和捕获的lambda表达式看看是否真的是这样,demo:

#include <thread>
#include <iostream> int main()
{
int n1 = 500;
int n2 = 600;
std::thread t([&](int addNum){
n1 += addNum;
n2 += addNum;
},500);
t.join();
std::cout << n1 << ' ' << n2 << std::endl;
}

得到了预期结果:

[thread]main
1000 1100
线程结束

在启动了一个线程(创建了一个thread对象)之后,当这个线程结束的时候(std::terminate()),我们如何去回收线程所使用的资源呢?thread库给我们两种选择:1.加入式(join()) 2.分离式(detach())。值得一提的是,你必须在thread对象销毁之前做出选择,这是因为线程可能在你加入或分离线程之前,就已经结束了,之后如果再去分离它,线程可能会在thread对象销毁之后继续运行下去。

Note that you only have to make this decision before the std::thread object is destroyed—the thread itself may well have finished long before you join with it or detach it, and if you detach it,then the thread may continue running long after the std::thread object is destroyed.-------《C++ Concurrency In Action》 2.1.1

join()字面意思是连接一个线程,意味着主动地等待线程的终止,上面的例子我都是使用了join()的方式。join()是这样工作的,在调用进程中join(),当新的线程终止时,join()会清理相关的资源(any storage associated with the thread),然后返回,调用线程再继续向下执行。正是由于join()清理了线程的相关资源,因而我们之前的thread对象与已销毁的线程就没有关系了,这意味着一个线程的对象每次你只能使用一次join(),当你调用的join()之后joinable()就将返回false了。光靠文字还是略显苍白的,肯定还是代码更加直观:

#include <iostream>
#include <thread> void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
} int main()
{
std::thread t(foo);
std::cout << "before joining,joinable=" << std::boolalpha << t.joinable() << std::endl;
t.join();
std::cout << "after joining, joinable=" << std::boolalpha << t.joinable() << '\n';
}

运行结果:

[thread]main
before joining,joinable=true
after joining, joinable=false

第二种方式是分离式,对应的函数是detach()。detach这个词的意思是分离的意思,对一个thread对象使用detach()意味着从调用线程分理出这个新的线程,我们称分离的线程叫做守护线程(daemon threads)。之后也就不能再与这个线程交互。打个不太恰当的比方,就像是你和你女朋友分手(你可能说我好坏,为什么不说是我和我的女朋友?因为我没有女朋友啊,哈哈,看我多机智。),那之后你们就不会再有联系(交互)了,而她的之后消费的各种资源也就不需要你去埋单了(清理资源)。既然没有交互,也就谈不上join()了,因此调用joinable()必然是返回false。分离的线程会在后台运行,其所有权(ownership)和控制权将会交给c++运行库。同时,C++运行库保证,当线程退出时,其相关资源的能够正确的回收。

分离的线程,大致上是这样执行的,它运行结束后,不再需要通知调用它的线程:

线程的标识符

在thread类中有一个叫做_M_id的私有变量用来标识线程,其类型是std::thread::id,在GNU上实现如下:

class thread
{
...
class id
{
native_handle_type _M_thread;
public:
id() noexcept : _M_thread() { }
explicit
id(native_handle_type __id) : _M_thread(__id) { }
private:
friend class thread;
friend class hash<thread::id>;
friend bool
operator==(thread::id __x, thread::id __y) noexcept
{ return __gthread_equal(__x._M_thread, __y._M_thread); }
friend bool
operator<(thread::id __x, thread::id __y) noexcept
{ return __x._M_thread < __y._M_thread; }

template<class _CharT, class _Traits>
friend basic_ostream<_CharT, _Traits>&
operator<<(basic_ostream<_CharT, _Traits>& __out, thread::id __id);
}; private:
id _M_id;
public:
thread::id
get_id() const noexcept
{
return _M_id;
}
...
};

代码还是比较清晰的,很明显我们可以通过std::this_thread::get_id()这个函数获取线程的标识符,由于上面的代码中thread::id类中重载了operator “<<”运算符,因此我们可以对id类型进行输出。同时,当一个thread对象并没有关联一个线程的时候(可能thread对象是默认初始化的或者初始化的线程已经运行结束被join()或者是线程已经detach()),这时候get_id()将返回默认构造的id对象,意味着这个thread对象不存在关联的线程,输出可能像是这样的:“thread::id of a non-executing thread”。与此同时,我们也可以在当前的线程中获取当前线程的线程标识符,方法比较简单直接调用std::this_thread::get_id()即可。

现在,我们写一个使用标准输出尝试输出线程id的demo:

#include <iostream>
#include <thread> void fun()
{
std::cout << std::this_thread::get_id() << std::endl;
} int main()
{
std::thread t(fun);
std::cout << t.get_id() << std::endl;
t.join();
}

  其输出结果是一个15位的整数,具体取决于实现,当然具体怎么实现并无区别,我们只要关心它可以作为标识线程的一种手段:

[thread]main
140302328772352
140302328772352

同时,std::thread::id中还重载了operator==,这样就允许我们去比较两个线程是否相等(是否是同一个线程),比如我们需要给不同的线程分配任务或者限制某个线程的操作,id类型实现了这样的比较运算给了我们编程时极大的便利。

      关于何时用到std::thread::id::operator<,我暂时没有搞清楚,如果您知道,不妨告诉我,我将万分感激。

      理解了以上内容,我们基本可以使用多线程去实现一些简单的任务了,当然要想安全地使用线程,这还是远远不够的。接下来我还要再探、三探thread库。

如若以上博文有错误、误导之处,请你原谅,还望批评指正,我在此先谢过各位。

漫谈c++11 Thread库之使写多线程程序的更多相关文章

  1. 漫谈C++11 Thread库之原子操作

    我在之前一篇博文<漫谈C++11 Thread库之使写多线程程序>中,着重介绍了<thread>头文件中的std::thread类以及其上的一些基本操作,至此我们动手写多线程程 ...

  2. c++11 Thread库写多线程程序

    一个简单的使用线程的Demo c++11提供了一个新的头文件<thread>提供了对线程函数的支持的声明(其他数据保护相关的声明放在其他的头文件中,暂时先从thread头文件入手吧),写一 ...

  3. 通过java.util.concurrent写多线程程序

    在JDK 1.5之前,要实现多线程的功能,得用到Thread这个类,通过这个类设计多线程程序,需要考虑性能,死锁,资源等很多因素,一句话,就是相当麻烦,而且很容易出问题.所幸的是,在JDK1.5之后, ...

  4. Boost::thread库的使用

    阅读对象 本文假设读者有几下Skills [1]在C++中至少使用过一种多线程开发库,有Mutex和Lock的概念. [2]熟悉C++开发,在开发工具中,能够编译.设置boost::thread库. ...

  5. c++11 thread的学习

    http://www.cnblogs.com/wxquare/p/6736202.html 还没开始 留个链接 使用c++11 thread支持实现  一个生产者消费者模型 下面是一个生产者消费者问题 ...

  6. C++11 标准库也有坑(time-chrono)

    恰巧今天调试程序遇到时间戳问题, 于是又搜了搜关于取时间戳,以及时间戳转字符串的问题, 因为 time()   只能取到秒(win和linux) 想试试看能不能找到 至少可以取到毫秒的, 于是, 就找 ...

  7. 大一C语言学习笔记(11)---编程篇--写一个程序,可以获取从键盘上输入的的三个数,并能够判断是否可以以这三个数字作为边长来构成一个三角形,如果可以的话,输出此三角形的周长及面积,要求 0 bug;

    考核内容: 写一个程序,可以获取从键盘上输入的的三个数,并能够判断是否可以以这三个数字作为边长来构成一个三角形,如果可以的话,输出此三角形的周长及面积: 答案: #include<stdio.h ...

  8. c++11 线程:让你的多线程任务更轻松

      介绍 本文旨在帮助有经验的Win32程序员来了解c++ 11线程库及同步对象 和 Win32线程及同步对象之间的区别和相似之处. 在Win32中,所有的同步对象句柄(HANDLE)是全局句柄.它们 ...

  9. 【转】介绍Jython,第一部分:轻轻松松写JAVA程序

    本文转自:http://www.ibm.com/developerworks/cn/education/java/j-jython1/index.html 关于本教程 本教程介绍哪些内容? 这个两部分 ...

随机推荐

  1. QTableView中修改某个单元格或者行或者列内容颜色

    QTableView的单元格内容实现还是继承了TableViewModel类的data(const QModelIndex &index, int role) const函数,那个设置颜色的问 ...

  2. Xdebug文档(六) 分析PHP脚本

    分析PHP脚本Xdebug内置分析器能让你找到脚本中的瓶颈并用额外的工具诸如KcacheGrind或WinCacheGrind工具可视化. 介绍 Xdebug分析器是分析PHP代码和判断瓶颈或确定代码 ...

  3. Netty(六)UDP在netty中的使用

    关于UDP的介绍,这里不在阐述.相比于TCP而言,UDP不存在客户端和服务端的实际链接,因此不需要为连接(ChannelPipeline)设置handler. 服务端: public void run ...

  4. 前端弹出对话框 js实现 ajax交互

    原本计划实现这样一个需求: 前台点击触发某业务动作,需要用户补充信息,不做跳转页面,弹窗的形式进行补充信息. 折腾出来了,但是最终没有用到. 代码还有些毛躁,提供大概实现逻辑. 实现思路: 在窗口铺上 ...

  5. 1-2 nodejs小节 文件读取

    1.表达式 在命令行输入  node回车后,可以在后边输入相应的表达式,进行运算操作   2.阻塞文件读取 var data=fs.readFileSync('input.txt', 'utf-8') ...

  6. 第一次react-native项目实践要点总结

    今天完成了我的第一个react-native项目的封包,当然其间各种环境各种坑,同时,成就感也是满满的.这里总结一下使用react-native的一些入门级重要点(不涉及环境).注意:阅读需要语法基础 ...

  7. jquery获取关于宽度和高度的尺寸问题

    作者原创,转载请出名出处! console.log("可视区高度:"+$(window).height()); console.log("可视区宽度:"+$(w ...

  8. 那些过目不忘的H5页面

    原文链接:http://isux.tencent.com/great-mobile-h5-pages.html 从引爆朋友圈的H5小游戏<围住神经猫>,到颠覆传统广告的大众点评H5专题页& ...

  9. SharePoint 2013 安装中间出错了怎么办? 每一次安装都是一段曲折的路【1603(0x643) 】

    今天安装SharePoint 2013又出现了如下的错误,所有的必备软件都已经安装成功的情况下: 如何解决这样的问题呢? 1.首先把安装的日志文件找出来:位于 C:\用户\您的用户名\AppData\ ...

  10. IOS开发基础知识--碎片46

    1:带中文的URL处理 // http://static.tripbe.com/videofiles/视频/我的自拍视频.mp4 NSString *path = (__bridge_transfer ...