1,简介

C++11中加入了<thread>头文件,此头文件主要声明了std::thread线程类。C++11的标准类std::thread对线程进行了封装,定义了C++11标准中的一些表示线程的类、用于互斥访问的类与方法等。应用C++11中的std::thread便于多线程程序的移值。

std::thread类成员函数:

(1)、get_id:获取线程ID,返回一个类型为std::thread::id的对象。

(2)、joinable:检查线程是否可被join。检查当前的线程对象是否表示了一个活动的执行线程。缺省构造的thread对象、已经完成join的thread对象、已经detach的thread对象都不是joinable。

(3)、join:调用该函数会阻塞当前线程(主调线程)阻塞调用者(caller)所在的线程(主调线程)直至被join的std::thread对象标识的线程(被调线程)执行结束。

(4)、detach:将当前线程对象所代表的执行实例与该线程对象分离,使得线程的执行可以单独进行。一旦线程执行完毕,它所分配的资源将会被释放。

(5)、native_handle:该函数返回与std::thread具体实现相关的线程句柄。native_handle_type是连接thread类和操作系统SDK API之间的桥梁,如在Linux g++(libstdc++)里,native_handle_type其实就是pthread里面的pthread_t类型,当thread类的功能不能满足我们的要求的时候(比如改变某个线程的优先级),可以通过thread类实例的native_handle()返回值作为参数来调用相关的pthread函数达到目录。This member function is only present in class thread if the library implementation supports it. If present, it returns a value used to access implementation-specific information associated to the thread.

(6)、swap:交换两个线程对象所代表的底层句柄。

(7)、operator=:moves the thread object

(8)、hardware_concurrency:静态成员函数,返回当前计算机最大的硬件并发线程数目。基本上可以视为处理器的核心数目。

另外,std::thread::id表示线程ID,定义了在运行时操作系统内唯一能够标识该线程的标识符,同时其值还能指示所标识的线程的状态。Values of this type are returned by thread::get_id and this_thread::get_id to identify threads.

有时候我们需要在线程执行代码里面对当前调用者线程进行操作,针对这种情况,C++11里面专门定义了一个命名空间this_thread,此命名空间也声明在<thread>头文件中,其中包括get_id()函数用来获取当前调用者线程的ID;yield()函数(yield,放弃的意思)可以用来将调用者线程跳出运行状态,重新交给操作系统进行调度,即当前线程放弃执行,操作系统调度另一线程继续执行;sleep_until()函数是将线程休眠至某个指定的时刻(time point),该线程才被重新唤醒;sleep_for()函数是将线程休眠某个指定的时间片(time span),该线程才被重新唤醒,不过由于线程调度等原因,实际休眠实际可能比sleep_duration所表示的时间片更长。

1.创建一个线程

创建线程比较简单,使用std的thread实例化一个线程对象就创建完成了,示例:

#include <iostream>
#include <thread>
#include <stdlib.h> //sleepusing namespace std;void t1()  //普通的函数,用来执行线程
{for (int i = 0; i < 10; ++i){cout << "t1111\n";sleep(1);}
}
void t2()
{for (int i = 0; i < 20; ++i){cout << "t22222\n";sleep(1);}
}
int main()
{thread th1(t1);  //实例化一个线程对象th1,使用函数t1构造,然后该线程就开始执行了(t1())thread th2(t2);th1.join(); // 必须将线程join或者detach 等待子线程结束主进程才可以退出th2.join(); //or use detach//th1.detach();//th2.detach();cout << "here is main\n\n";return 0;
}

上述提到的问题,还可以使用detach来解决,detach是用来和线程对象分离的,这样线程可以独立地执行,不过这样由于没有thread对象指向该线程而失去了对它的控制,当对象析构时线程会继续在后台执行,但是当主程序退出时并不能保证线程能执行完。如果没有良好的控制机制或者这种后台线程比较重要,最好不用detach而应该使用join。

2, mutex和std::lock_guard的使用

头文件是#include <mutex>,mutex是用来保证线程同步的,防止不同的线程同时操作同一个共享数据。

但使用lock_guard则相对安全,它是基于作用域的,能够自解锁,当该对象创建时,它会像m.lock()一样获得互斥锁,当生命周期结束时,它会自动析构(unlock),不会因为某个线程异常退出而影响其他线程。示例:

#include <iostream>
#include <thread>
#include <mutex>
#include <stdlib.h>int cnt = 20;
std::mutex m;
void t1()
{while (cnt > 0){    std::lock_guard<std::mutex> lockGuard(m);// std::m.lock();if (cnt > 0){//sleep(1);--cnt;std::cout << cnt << std::endl;}// std::m.unlock();}
}
void t2()
{while (cnt > 0){std::lock_guard<std::mutex> lockGuard(m);// std::m.lock();if (cnt > 0){--cnt;std::cout << cnt << std::endl;}// std::m.unlock();}
}int main(void)
{std::thread th1(t1);std::thread th2(t2);th1.join();    //等待t1退出th2.join();    //等待t2退出std::cout << "here is the main()" << std::endl;return 0;
}

输出结果,cnt是依次递减的,没有因为多线程而打乱次序::

19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
here is the main()

3,详解

绍一下thread的用法。

std::thread 构造函数

thread() noexcept; (1) (C++11 起)
thread( thread&& other ) noexcept; (2) (C++11 起)
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
(3) (C++11 起)
thread(const thread&) = delete; (4) (C++11 起)

构造新的 thread 对象。

1) 构造不表示线程的新 thread 对象。

2) 移动构造函数。构造表示曾为 other 所表示的执行线程的 thread 对象。此调用后 other 不再表示执行线程。

3) 构造新的 std::thread 对象并将它与执行线程关联。新的执行线程开始执行

4) 复制构造函数被删除; thread 不可复制。没有两个 std::thread 对象可表示同一执行线程。

std::thread::~thread

~thread();

  (C++11 起)

在下列操作后 thread 对象无关联的线程(从而可安全销毁)

  • 被默认构造
  • 被移动
  • 已调用 join()
  • 已调用 detach()

std::thread::operator=

thread& operator=( thread&& other ) noexcept;

(1) (C++11 起)

这个例子是借鉴cppreference.com这个网站上面的,我详细的分析一下这个例子。

#include <iostream>
#include <utility>
#include <thread>
#include <chrono>void f1(int n){for (int i = 0; i < 2; ++i) {std::cout << "Thread 1 executing\n";++n;std::this_thread::sleep_for(std::chrono::milliseconds(10));}
}void f2(int& n){for (int i = 0; i < 2; ++i) {std::cout << "Thread 2 executing\n";++n;std::this_thread::sleep_for(std::chrono::milliseconds(10));}
}class foo{
public:void bar(){for (int i = 0; i < 2; ++i) {std::cout << "Thread 3 executing\n";++n;std::this_thread::sleep_for(std::chrono::milliseconds(10));}}int n = 0;
};class baz{
public:void operator()(){int n = 0;for (int i = 0; i < 2; ++i) {std::cout << "Thread 4 executing\n";++n;std::this_thread::sleep_for(std::chrono::milliseconds(10));}}
};int main(){int n = 0;foo f;baz b;std::thread t1; // t1 不是线程std::thread t2(f1, n + 1); // 按值传递std::thread t3(f2, std::ref(n)); // 按引用传递std::thread t4(std::move(t3)); // t4 现在运行 f2() 。 t3 不再是线程std::thread t5(&foo::bar, &f); // t5 在对象 f 上运行 foo::bar()std::thread t6(b); // t6 在对象 b 上运行 baz::operator()t1 = std::thread(f1, n + 1); //t1等号运算符重载std::thread t7(std::thread(f2, std::ref(n))); //t7拷贝构造t2.join();t4.join();t5.join();t6.join();t1.join();t7.join();std::cout << "Final value of n is " << n << '\n';std::cout << "Final value of foo::n is " << f.n << '\n';
}


可以看到只是调用默认构造函数(构造函数(1))构造线程,线程是不会被构造的,如图,线程t1没有被构造。

线程t2,t3调用构造函数(3) ,参数Function&& f, Args&&... args,f实质上是一个函数指针,保存线程所要执行的函数的地址,args是函数的参数,线程运行f(args),线程t5因为调用的是类的成员方法,this指针作为非静态成员函数的隐含形参,因此我们将this指针即就是对象的地址传进去。

线程t4调用拷贝构造函数(构造函数(2)),thread的拷贝构造函数只有右值拷贝构造,左值的拷贝构造被删除了,右值引用是指函数的参数可以是临时变量,如线程t7,由一个临时变量std::thread(f2, std::ref(n))构造t7。而t4使用的std::move(t3),其中

template< class T >
typename std::remove_reference<T>::type&& move( T&& t ) noexcept;

std::move返回的是一个右值引用的变量,而函数的作用是将t到另一对象有效率的资源传递,传递过后,资源t会被销毁。在这个表达式中的作用就是将线程t3的资源传递给t4,并销毁t3。

线程t6是调用构造函数(3),而这个类里面有operator()(),括号的运算符重载,只要这个对象被使用,那么就会调用括号运算符重载。

线程t1调用等号运算符重载。

最后运行的结果:

线程2-5运行了两遍,所以上面的函数分别打印了2遍,另外线程1,7又将函数f1和f2打印了2遍。

std::thread::joinable

bool joinable() const noexcept;

  (C++11 起)

检查线程是否可合并,即潜在地运行于平行环境中

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

我们可以看到,在线程为活跃之前,joinable的值为false,在线程活跃之后,joinable的值为true。在线程被join,detach之后,置joinable的值为false。joinable被置为false的情况有

  • 被默认构造
  • 被移动
  • 已调用 join()
  • 已调用 detach()

这些情况和线程被析构的情况都是一样的,因为thread的析构函数是这样实现的

joinable为true的话,会调用terminate(),terminate()会调用abort()来终止程序,程序会报错,因此只有joinable变成false,才能执行析构函数,因此两者运行的必要条件是一样的。

std::thread::join

void join();

  (C++11 起)

等待线程完成其执行

std::thread::detach

void detach();

  (C++11 起)

容许线程从线程句柄独立开来执行。

join和detach执行的必要条件都是joinable是true。

join和detach的区别,join会阻塞当前的线程,直到运行的线程结束,比如在main函数里面调用线程thread,那么main函数里面调用thread后,会先去执行thread中的代码逻辑,直到其结束,再去执行main函数里面的代码逻辑。调用join后,所有分配的资源都会被释放。在调用了join之后,*this就不会拥有任何线程了。

detach从线程对象中分离出执行线程,允许线程独立的执行。一旦线程退出,所有分配的资源都会被释放。在调用了detach之后,*this就不会拥有任何线程了。

以上图片借鉴https://www.cnblogs.com/ittinybird/p/4820142.html,这篇文章也写得很好,大家可以看一下。

可能光说还是有点抽象,上代码:

#include <iostream>
#include <thread>
#include <chrono>void foo(){for (int i = 0; i < 5; ++i){std::cout << "void foo()" << std::endl;}
}void bar(){for (int i = 0; i < 5; ++i) {std::cout << "void bar()" << std::endl;}
}int main(){std::thread helper1(foo);std::thread helper2(bar);helper1.join();helper2.join();for (int i = 0; i < 5; ++i){std::cout << "done!\n";}
}#include <iostream>
#include <thread>
#include <chrono>void foo(){for (int i = 0; i < 5; ++i){std::cout << "void foo()" << std::endl;}
}void bar(){for (int i = 0; i < 5; ++i) {std::cout << "void bar()" << std::endl;}
}int main(){std::thread helper1(foo);std::thread helper2(bar);helper1.detach();helper2.detach();for (int i = 0; i < 5; ++i){std::cout << "done!\n";}system( "pause");
}

可以看出来,detach(拆卸,分开)不会阻塞主线程,主线程和子线程是同时运行的。而join(合并)是会阻塞主线程,等待子线程运行完后,再运行主线程。

C++ thread用法总结(整理)相关推荐

  1. matlab i型级联filter,Matlab中filter,conv,impz用法(最新整理)

    <Matlab中filter,conv,impz用法(最新整理)>由会员分享,可在线阅读,更多相关<Matlab中filter,conv,impz用法(最新整理)(5页珍藏版)> ...

  2. c语言pair的用法,C++ pair的基本用法总结整理

    1,pair的应用 pair是将2个数据组合成一组数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存.另一个应用是,当一个函数需要返回2个数据的时候, ...

  3. C++多线程thread用法

    1.多线程相关的类 C++11 新标准中引入了五个头文件来支持多线程编程,他们分别是<atomic> ,<thread>,<mutex>,<condition ...

  4. C 的Pair用法分类整理(精)

    1 pair的应用 pair是将2个数据组合成一个数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存.另一个应用是,当一个函数需要返回2个数据的时候, ...

  5. android thread 用法,我们如何在Android中使用runOnUiThread?

    暮色呼如 下面是更正的runThread功能.private void runThread() { new Thread() { public void run() { while (i++ try  ...

  6. php放量文档,MACD高级用法(论坛整理版)讲稿.docx

    MACD高级用法目录趋势停顿与转折(MACD高级战法)2 谈谈零轴3 再谈背离5 来一招通过零轴找买点的实例10 今天的 实盘与大盘解读11 底抄的爽吧12 零轴操作实例213 战斗一天16 仔细看图 ...

  7. Kendo UI Grid 用法详细整理

    目录 一. kendo UI grid结构 结构实例 二. kendo UI grid常用属性 1.new kendo.data.DataSource()中常见属性: 2.$("#grid& ...

  8. htaccess文件用法收集整理

    1.时区设置 有些时候,当你在PHP里使用date或mktime函数时,由于时区的不同,它会显示出一些很奇怪的信息.下面是解决这个问题的方法之一.就是设置你的服务器的时区.你可以在这里找到所有支持的时 ...

  9. 最完的htaccess文件用法收集整理

    1.时区设置 有些时候,当你在PHP里使用date或mktime函数时,由于时区的不同,它会显示出一些很奇怪的信息.下面是解决这个问题的方法之一.就是设置你的服务器的时区.你可以在这里找到所有支持的时 ...

最新文章

  1. NSThread 多线程相关
  2. JS中的prototype
  3. qt on android qml,Qt on Android: Qt Quick 之 Hello World 图文详解
  4. aspnetpager分页,不使用存储过程
  5. cmd中检测远程的ip和端口是否处于监听状态
  6. 单选按钮带文字_计算机二级MS office高级应用历年真题操作题文字解析
  7. js网页顶部线性页面加载进度条,jquery头部线性进度条总结
  8. jedis操作set_Jedis对redis的五大类型操作代码详解
  9. JQuery的Alert插件介绍
  10. MITRE 发布 2020 CWE Top 25 榜单
  11. 解决Vmware虚拟机中没有网络连接Ubuntu无法上网
  12. python_知识点_字符串+数字+列表
  13. 如何从UCI获取数据集?
  14. 微信小程序组件之间传值
  15. day16-17-18.对象序列化和反序列化、API获取数据、python操作Excel/CSV文件、类、面向对象编程(初级及进阶)、继承
  16. com.android.provision基本介绍
  17. 牛顿法及其下山法+C代码
  18. 获取移动端ip的方法
  19. AutoCAD生成png透明图像
  20. SIMULINK rlc电路仿真

热门文章

  1. 铁电存储器FRAM的特性
  2. UNI-APP_在uni-app中引入和使用uViewUI
  3. python实验猜数游戏
  4. 让VMware Workstation(虚拟主机)可以游戏
  5. node.js目录结构
  6. 拉伯证券|锂离子动力电池有哪些优缺点?锂离子电池的优缺点详解
  7. 用matlab生成RGB三色的散点图
  8. 简单的javascript学习01
  9. zoom走了,谁的机会来了
  10. 专科云计算的出路在哪里?