C++标准线程库之入门

自c++11版本后,标准库也提供了对线程的支持。虽然大多场合还是使用其他的三方线程库,如:boost::thread, QThread等,但是学习下还是有必要的。

1. std::thread简介

std::thread类即创建子线程的类,定义于头文件thread中。

std::thread类仅不到十个公开成员函数,同时无法进行拷贝,只能使用移动构造和赋值转移所有权。

2. CPU支持的线程并发数

#include 
[static]
unsigned int std::thread::hardware_concurrency();
// 返回硬件支持的线程数目,失败返回0
#include 
#include 

int main()
{
    std::cout << "CPU是" << std::thread::hardware_concurrency() << "线程" << std::endl;
    return 0;
}

3. 创建子线程

#include 
[public]
template<typename _Callable, typename... _Args>
std::thread::thread(_Callable&& __f, _Args&&... __args);
// 构造函数之一, 传一个可以调用的函数或者对象作为任务, 有子线程来处理运行这个任务。
// 未调用此构造函数的线程是无法执行的,称为空任务线程。
// 需要明确的是构造完成后,子线程即开始执行,而不是等join(结合)或者detach(分离)调用才执行

bool std::thread::joinable() const;
// 未被结合或者分离的非空任务线程会返回true, 否则返回false。

std::thread::id std::thread::get_id() const;
// 未被结合或者分离的非空任务线程会返回非0的整数作为子线程的标识符
#include 
#include 

void func(int a)
{
    std::cout << "This function is callable: " << a << std::endl;
}

class callable_obj
{
public:
    // 对象可以有类似函数的调用
    void operator ()(int a)
    {
        std::cout << "This object is callable: " << a << std::endl;
    }
};

int main()
{
    // 可调用的函数初始化线程
    std::thread t1(func, 1);
    // // t1.get_id(): 很大的数  t1.joinable(): true
    std::cout << "t1 thread_id: " << t1.get_id() << " joinable: " << t1.joinable() << std::endl;

    
    // 可调用的对象初始化线程
    std::thread t2;
    // t2.get_id(): 0  t2.joinable(): false
    std::cout << "t2 thread_id: " << t2.get_id() << " joinable: " << t2.joinable() << std::endl;
    callable_obj obj;
    // 移动赋值, 也可以用t1的直接构造
    t2 = std::move(std::thread(obj, 2));
    // t2.get_id(): 很大的数  t2.joinable(): true
    std::cout << "t2 thread_id: " << t2.get_id() << " joinable: " << t2.joinable() << std::endl;

    return 0;
}
t1 thread_id: 140549800912640 joinable: This function is callable: 11
t2 thread_id: thread::id of a non-executing thread joinable: 0

t2 thread_id: 140549792519936 joinable: 1
terminate called without an active exception
This object is callable: 2

需要主要的我们并没有显示执行t1和t2线程,但是线程还是执行了,但是会发生异常。

4. 子线程等待方式

#include 
[public]
void std::thread::join();
// 仅当joinable为true才能调用本函数, 否则抛出异常system_error。
// 本函数是使主线程进入阻塞状态,等待子线程执行完成后,主线程在继续执行,joinable函数返回false。
// 当子线程执行结束后,资源由主线程回收,主线程继续执行后续指令。

void std::thread::detach();
// 仅当joinable为true才能调用本函数, 否则抛出异常system_error。
// 本函数调用后主线程不会去等待子线程是否执行完成,主线程继续执行后续指令,joinable函数返回false。
// 可能会发生主线程已经执行结束,子线程还未运行结束的情况。
// 当线程结束时,分离的线程由系统回收。
#include 
#include 

void func(int a)
{
    std::cout << "This function is callable: " << a << std::endl;
}

class callable_obj
{
public:
    void operator ()(int a)
    {
        std::cout << "This object is callable: " << a << std::endl;
    }
};

int main()
{
    std::thread t1(func, 1);
    // 分离,后面的代码继续处理
    t1.detach();

    callable_obj obj;
    std::thread t2(std::thread(obj, 2));
    // 线程进入阻塞, t2线程执行完成才会执行return 0
    t2.join();
	
    return 0;
}
This function is callable: 1
This object is callable: 2

本实例并没有什么不对, 都会输出,因为主线程处理后续代码的时间较长, t1大概率会在主线程执行完执行结束。因此,不会发生什么特殊的情况,但是很多时候分离需要考虑到这个问题。

5. 传参问题

传参问题还需要多注意下,会出现一些问题。在参数是值的情况正常不会发生,但是如果参数有引用就会发生错误,需要使用std::ref来告诉其传递的是引用 。

#include 
#include 
#include 

void func(int a, int& b)
{
    std::cout << "func: a = " << a << ", b = " << b << std::endl;
    b  = 7;
}

int main()
{
    int a = 2, b = 2;
    // std::thread t(func, a, b) 错误
    std::thread t(func, a, std::ref(b));
    t.join();
    std::cout << "main: a = " << a << ", b = " << b << std::endl;
    return 0;
}

func: a = 2, b = 2
main: a = 2, b = 7

6. 对线程类的薄封装(RAll)

#include 
#include 
#include 

void func(int a, int& b)
{
    std::cout << "func: a = " << a << ", b = " << b << std::endl;
    b  = 7;
}

class thread_graud
{
    std::thread _t;
public:
    thread_graud(std::thread& t) : _t(std::move(t)) {}

    ~thread_graud()
    {
        if(_t.joinable()) _t.join();
    }

    thread_graud(const thread_graud&) = delete;
    thread_graud& operator =(const thread_graud&) = delete;
};

int main()
{
    int a = 2, b = 2;
    std::thread t(func, a, std::ref(b));
    thread_graud g(t);
    std::cout << "main: a = " << a << ", b = " << b << std::endl;
    return 0;
}

使用中间类thread_graud来隐式调用join方法。

7. 扩展

c++11的线程是可以使用平台的特性扩展的,没想到吧。

#include 

std::native_handle_type std::native_handle();
// 本函数会返回对于平台下底层句柄, 其条件也是joinable为true, 本人的系统是linux,此函数就会返回pthread_t
#include 
#include 
#include 

void func(int a)
{
    std::cout << "linux 系统调用成功 a = " << a << std::endl;
}

int main()
{
    int a = 2;
    std::thread t(func, a);
    // 使用linux平台下的pthread_join来调用线程
    pthread_join(t.native_handle(), nullptr);
    return 0;
}

linux 系统调用成功 a = 2
terminate called without an active exception

虽然可以这样来用, 但是由于使用linux下的线程函数启动了线程,而joinable的值并没有发生改变,线程对象析构时还是会调用std::terminate(),导致了异常的发生。

你可能感兴趣的:(C++标准库)