C++ std::thread概念介绍

C++ 11新标准中,正式的为该语言引入了多线程概念。新标准提供了一个线程库thread,通过创建一个thread对象来管理C++程序中的多线程。

本文简单聊一下C++多线程相关的一些概念及thread的基本用法。

0. 并行执行

程序并行执行两个必要条件:

  • 多处理器(multiple processors)or 多核处理器(multicore processors)
  • 软件并行

软件并发执行可分为两大类:

  1. 多线程并发  (同一个进程的多个线程并行);
  2. 多进程并发  (不同进程并行);

对于多线程,主要关注的是线程间的同步措施,用于确保线程安全;

对于多进程,主要关注的是进程间的通信机制,用于进程间传递消息和数据;

由于C++ 标准中没有多进程之间通信相关的标准,这些只能依赖于特定平台的API。本文只关注多线程相关。

1. C++多线程平台

C++11之前,window和linux平台分别有各自的多线程标准。使用C++编写的多线程往往是依赖于特定平台的。

  • Window平台提供用于多线程创建和管理的win32 api;
  • Linux下则有POSIX多线程标准,Threads或Pthreads库提供的API可以在类Unix上运行;

在C++11新标准中,可以简单通过使用hread库,来管理多线程。thread库可以看做对不同平台多线程API的一层包装;

因此使用新标准提供的线程库编写的程序是跨平台的。

2. pthread 或 C++ 11 thread

pthreads 是linux下的C++线程库,提供了一些线程相关的操作,比较偏向于底层,对线程的操作也是比较直接和方便的;

#include <pthread.h>
pthread_create (thread, attr, start_routine, arg) 

linux上对于pthread的使用需要连接pthread库(有些编辑器可能需要 -std=c++11):

g++ source.cpp -lpthread -o source.o 

尽管网上对C++ 11新标准中的thread类有很多吐槽,但是作为C++第一个标准线程库,还是有一些值得肯定的地方的,比如跨平台,使用简单。

而且新标准中可以方便的使用RAII来实现lock的管理等。

如果你想深入研究一下多线程,那么pthread是一个不错的选择。如果想要跨平台或者实现一些简单的多线程场景而不过多关注细节,那么权威的标准库thread是不二之选。

总之没有好与坏之分,适合就好。可以的话可以都了解一下。本文主要介绍后者。

3. 先理论后实践

对于多线程相关的学习,先弄清楚线程相关的一些概念,是很重要的。

比如线程安全、线程同步与互斥关系、线程如何通信、与进程的关系如何等。

不然实际写多线程程序是会碰到太多的问题,例如:

  • 程序死锁,无响应;
  • 执行结果不符合预期;
  • 多线程性能并没有很大提升;
  • 理不清程序执行流程;
  • 不知道怎么调试;
  • 程序运行时好时坏;

光线程安全就有很多理论要了解,这些光靠调试程序,根据结果来猜测是不可行的。

关于多线程相关的概念可以参考我之前以Python为例介绍线程的博文:

4. thread 多线程实例

看一下C++11 使用标准库thread创建多线程的例子:

 1 #include<iostream>
 2 #include<thread>
 3 #include<string>
 4
 5 using namespace std;
 6
 7 int tstart(const string& tname) {
 8     cout << "Thread test! " << tname << endl;
 9     return 0;
10 }
11
12 int main() {
13     thread t(tstart, "C++ 11 thread!");
14     t.join();
15     cout << "Main Function!" << endl;
16 }

多线程标准库使用一个thread的对象来管理产生的线程。该例子中线程对象t表示新建的线程。

4.1 标准库创建线程的方式

打开thread头文件,可以清楚的看到thread提供的构造函数。

  1. 默认构造函数                                         thread() noexcept;
  2. 接受函数及其传递参数的构造函数      template <class _Fn, class... _Args, ...> explicit thread(_Fn&& _Fx, _Args&&... _Ax)
  3. move构造函数                                       thread(thread&& _Other) noexcept;
  4. 拷贝构造函数                                         thread(const thread&) = delete;
  5. 拷贝赋值运算符                                     thread& operator=(const thread&) = delete;

其中拷贝构造函数和拷贝赋值运算符被禁用,意味着std::thread对象不能够被拷贝和赋值到别的thread对象;

默认构造函数构造一个空的thread对象,但是不表示任何线程;

接受参数的构造函数创建一个表示线程的对象,线程从传入的函数开始执行,该对象是joinable的;

move构造函数可以看做将一个thread对象对线程的控制权限转移到另一个thread对象;执行之后,传入的thread对象不表示任何线程;

int main()
{
    int arg = 0;
    std::thread t1;                        // t1 is not represent a thread
    std::thread t2(func1, arg + 1);     // pass to thread by value
    std::thread t3(func2, std::ref(arg));  // pass to thread by reference
    std::thread t4(std::move(t3));         // t4 is now running func2(). t3 is no longer a thread
    //t1.join()  Error!
    t2.join();
    //t3.join()  Error!
    t4.join();
}

多数情况下我们使用的是上面第二种创建线程的方式。下面看一下join和detach。

4.2  join && detach

对于创建的线程,一般会在其销毁前调用join和detach函数;

弄清楚这两个函数的调用时机和意义,以及调用前后线程状态的变化非常重要。

  • join 会使当前线程阻塞,直到目标线程执行完毕;

    • 只有处于活动状态线程才能调用join,可以通过joinable()函数检查;
    • joinable() == true表示当前线程是活动线程,才可以调用join函数;
    • 默认构造函数创建的对象是joinable() == false;
    • join只能被调用一次,之后joinable就会变为false,表示线程执行完毕;
    • 调用 ternimate()的线程必须是 joinable() == false;
    • 如果线程不调用join()函数,即使执行完毕也是一个活动线程,即joinable() == true,依然可以调用join()函数;
  • detach 将thread对象及其表示的线程分离;
    • 调用detach表示thread对象和其表示的线程完全分离;
    • 分离之后的线程是不在受约束和管制,会单独执行,直到执行完毕释放资源,可以看做是一个daemon线程;
    • 分离之后thread对象不再表示任何线程;
    • 分离之后joinable() == false,即使还在执行;

join实例分析

int main() {
    thread t(tstart, "C++ 11 thread!");
    cout << t.joinable() << endl;
    if (t.joinable()) t.join();
    //t.detach(); Error
    cout << t.joinable() << endl;
    // t.join(); Error
    cout << "Main Function!" << endl;
    system("pause");
}

简单来说就是只有处于活动状态的线程才可以调用join,调用返回表示线程执行完毕,joinable() == false.

inline void thread::join()
    {    // join thread
    if (!joinable())
        _Throw_Cpp_error(_INVALID_ARGUMENT);
    const bool _Is_null = _Thr_is_null(_Thr);    // Avoid Clang -Wparentheses-equality
    ... ...
}

将上面的t.join()换成是t.detach()会得到相同的结果.

void detach()
    {   // detach thread
    if (!joinable())
        _Throw_Cpp_error(_INVALID_ARGUMENT);
    _Thrd_detachX(_Thr);
    _Thr_set_null(_Thr);
    }

上面是thread文件中对detach的定义,可以看出只有joinable() == true的线程,也就是活动状态的线程才可以调用detach。

~thread() _NOEXCEPT
    {   // clean up
    if (joinable())
        _XSTD terminate();
    }

当线程既没有调用join也没有调用detach的时候,线程执行完毕joinable() == true,那么当thread对象被销毁的时候,会调用terminate()。

4.3 获取线程ID

线程ID是一个线程的标识符,C++标准中提供两种方式获取线程ID;

  1. thread_obj.get_id();
  2. std::this_thread::get_id()

有一点需要注意,就是空thread对象,也就是不表示任何线程的thread obj调用get_id返回值为0;

此外当一个线程被detach或者joinable() == false时,调用get_id的返回结果也为0。

cout << t.get_id() << ‘ ‘ << this_thread::get_id() << endl;
//t.detach();
t.join();
cout << t.get_id() << ‘ ‘ << std::this_thread::get_id() << endl;

4.4 交换thread表示的线程

除了上面介绍的detach可以分离thread对象及其所表示的线程,或者move到别的线程之外,还可以使用swap来交换两个thread对象表示的线程。

实例来看一下两个线程的交换。

int tstart(const string& tname) {
    cout << "Thread test! " << tname << endl;
    return 0;
}

int main() {
    thread t1(tstart, "C++ 11 thread_1!");
    thread t2(tstart, "C++ 11 thread_2!");
    cout << "current thread id: " << this_thread::get_id() << endl;
    cout << "before swap: "<< " thread_1 id: " << t1.get_id() << " thread_2 id: " << t2.get_id() << endl;
    t1.swap(t2);
    cout << "after swap: " << " thread_1 id: " << t1.get_id() << " thread_2 id: " << t2.get_id() << endl;
    //t.detach();
    t1.join();
    t2.join();
}

结果:

Thread test! C++ 11 thread_1!
Thread test! C++ 11 thread_2!
current thread id: 39308
before swap:  thread_1 id: 26240 thread_2 id: 37276
after swap:  thread_1 id: 37276 thread_2 id: 26240

下面是thread::swap函数的实现。

void swap(thread& _Other) _NOEXCEPT
    {   // swap with _Other
    _STD swap(_Thr, _Other._Thr);
    }

可以看到交换的过程仅仅是互换了thread对象所持有的底层句柄;

关于C++ 多线程新标准thread的基本介绍就到这里了,看到这里应该有一个简单的认识了。

关于线程安全和管理等高级话题,后面有空在写文章介绍。

原文地址:https://www.cnblogs.com/yssjun/p/11533346.html

时间: 2024-10-05 20:02:41

C++ std::thread概念介绍的相关文章

C++11 并发指南------std::thread 详解

参考: https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/blob/master/zh/chapter3-Thread/Introduction-to-Thread.md#stdthread-%E8%AF%A6%E8%A7%A3 本节将详细介绍 std::thread 的用法. std::thread 在 <thread> 头文件中声明,因此使用 std::thread 需包含 <thread> 头文件. &

C++11多线程std::thread的简单使用

转自:http://blog.csdn.net/star530/article/details/24186783 在cocos2dx 2.0时代,我们使用的是pthread库,是一套用户级线程库,被广泛地使用在跨平台应用上.但在cocos2dx 3.0中并未发现有pthread的支持文件,原来c++11中已经拥有了一个更好用的用于线程操作的类std::thread.cocos2dx 3.0的版本默认是在vs2012版本,支持c++11的新特性,使用std::thread来创建线程简直方便. 下面

[原]std::thread总结

原 总结 C++11 thread 缘起 std::thread 类定义 各个成员函数的简单介绍 例子 参考资料 缘起 从C++11开始提供了线程的支持,终于可以方便的编写跨平台的线程代码了.除了std::thread类,还提供了许多其它便利同步的机制,本篇总结是C++11学习笔记系列的首篇总结. std::thread std::thread定义在<thread>中,提供了方便的创建线程的功能. 类定义 class thread {  public:  thread() noexcept; 

C++——多线程编程(一)std::thread

(一)与C++11多线程相关的头文件 C++11 新标准中引入了四个头文件来支持多线程编程,他们分别是< atomic> ,< thread>,< mutex>,< condition_variable>和< future>. ?< atomic>:该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数. ?< thread>

std::thread(2)

个线程都有一个唯一的 ID 以识别不同的线程,std:thread 类有一个 get_id() 方法返回对应线程的唯一编号,你可以通过 std::this_thread 来访问当前线程实例,下面的例子演示如何使用这个 id: #include <thread> #include <iostream> #include <vector> void hello(){ std::cout << "Hello from thread " <

输入子系统概念介绍

输入子系统在内核中的位置:/driver/input drivers/input/input.c: input_init ---> err = register_chrdev(INPUT_MAJOR, "input", &input_fops); static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, }; 问:怎么读按键? inpu

Shiro权限控制框架入门1:Shiro的认证流程以及基本概念介绍

前言:我在最开始学习Shiro这个框架时,在网上搜索到的一个介绍比较全面的教程是:<跟我学Shiro>系列教程.但是在我看了他写的前几篇文章后,我发现虽然他在这个系列教程中把shiro的一些特性介绍地非常全面详细,但是整个教程的叙述方式还是有很大缺陷的.文章与文章之间并没有很好地串联起来,每篇文章介绍的东西都过于分散了,如果是对shiro完全不了解的新手来看的话完全是一场噩梦.就像一个网友评价的这样: 看了看这个教程,看完之后都想放弃shiro了,完全看不懂,后来百度了很多别的资料才理解了sh

足彩基础知识入门(4)赛事数据库与预测平台基础概念介绍(一)

在足球赛事数据库以及统计分析预测平台中,有很多概念,如果不搞懂,很难进行下一步的工作.所以为了配合团队人员的学习和任务进行,特意编写这篇文章.如果有其他问题和不懂的,请留言,将根据情况进行更新. 本文原文地址:足彩基础知识入门(4)赛事数据库与预测平台基础概念介绍(一) 1.指数1/2/3.... 我在 足彩基础知识入门(3)足彩赔率的本质 一文中介绍了赔率的概念,那么指数的概念和赔率以及结果是相关的.我们举个例子: 如上图的比赛,前面是竞彩非让球的赔率:1.74-3.25-4.15,也就是说

攻城狮在路上(贰) Spring(二)--- Spring IoC概念介绍

一.IoC的概念: IoC(控制反转)是Spring容器的核心.另一种解释是DI(依赖注入),即让调用类对某一个接口的依赖关系由第三方注入,以移除调用类对某一个接口实现类的一览. 定义如此,由此可见,在面向接口编程的情况下,IoC可以很好的实现解耦,可以以配置的方式为程序提供所需要的接口实现类. 在实际程序开发中,我们只需要提供对应的接口及实现类,然后通过Spring的配置文件或者注解完成对依赖类的装配.二.IoC的类型: 1.通过构造函数: 此种方式的缺点是,在构造函数中注入之后一般会作为一个