C++并发编程指南2

线程管理基础

每个程序至少有一个线程:执行main()函数的线程,其余线程有其各自的入口函数。线程与原始线程(以main()为入口函数的线程)同时运行。如同main()函数执行完会退出一样,当线程执行完入口函数后,线程也会退出。在为一个线程创建了一个 std::thread 对象后,需要等待这个线程结束;不过,线程需要先进行启动。

启动线程

C++线程启动可以归结为构造std::thread对象:

void do_some_work();
std::thread my_thread(do_some_work);

其他构造std::thread对象方法:

  • 普通函数
  • 列表初始化
  • 仿函数:只要重载一个类的operator()方法
  • lambda表达式
#include <iostream>
#include <thread>
using namespace std;

void hello()
{
    cout << "hello concurrent world" << endl;
}

class background_task
{
public:
    void operator()() const 
    {
        cout << "class constructor" << endl;
    }
};

int main()
{
    thread t(hello); //启动一个新线程,执行hello
    t.join();

    thread t3{hello};
    t3.join();

    background_task bt;
    thread t2(bt);
    t2.join();

    thread t4([] {
        cout << "lambda" << endl;
    });
    t4.join();

    return 0;
}

等待线程

join():确保局部变量在线程完成后,才被销毁。清理了线程相关的存储部分,这样 std::thread 对象将不再与已经完成的线程有任何关联。
通常,当倾向于在无异常的情况下使用join()时,需要在异常处理过程中调用join(),从而避免生命周期的问题。当然也可以使用RAII手法让线程退出时自动调用join()

detach:分离一个线程。使用detach()会让线程在后台运行,这就意味着主线程不能与之产生直接交互。也就是说,不会等待这个线程结束;如果线程分离,那么就不可能有 std::thread 对象能引用它。通常称分离线程为守护线程(daemon threads),UNIX中守护线程是指,没有任何显式的用户接口,并在后台运行的线程。

向线程函数传递参数

注意:默认参数要拷贝到线程独立内存中,即使参数是引用的形式,也可以在新线程中进行访问。

  • 在传递给std::thread构造函数之间就要将字面值转为对应的类型,以防止隐式转换失败。
void f(int i, string const & s)
{
    cout << i << ":" << s << endl;
}

int main()
{
    //1. 可能转换崩溃,导致线程出现未定义行为。
    //thread t(f, 3, "hello"); 

    //2. 使用std::string构造函数, OK
    thread t(f, 3, std::string("hello"));
    t.join();

    return 0;
}
  • 需要传递一个引用时。可以使用 std::ref 将参数转换成引用的形式
  • 可以通过std::bind传递函数及参数给std::thread构造函数。
class X
{
public:
    void do_work(int i)
    {
        cout << i << endl;
    }
};

int main()
{
    X my_x;
    int num(2);
    std::thread t(&X::do_work, &my_x, num);

    t.join();

    return 0;
}
  • std::bind提供的参数可以移动,但不能拷贝。"移动"是指:原始对象中的数据转移给另一对象,而转移的这些数据就不再在原始对象中保存了。
  • 当原对象是一个临时变量时,自动进行移动操作,但当原对象是一个命名变量,那么转移的时候就需要使用 std::move() 进行显示移动。
  • 标准线程库中和 std::unique_ptr 在所属权上有相似语义类型的类有好几种, std::thread 为其中之一。
  • 执行线程的所有权可以在多个 std::thread 实例中互相转移,这是依赖于 std::thread 实例的可移动且不可复制性。不可复制保性证了在同一时间点,一个 std::thread 实例只能关联一个执行线程;可移动性使得程序员可以自己决定,哪个实例拥有实际执行线程的所有权。

转移线程所有权

多个thread对象之间转移

  • C++标准库中有很多资源占有(resource-owning)类型,比如 std::ifstream , std::unique_ptr 还有 std::thread 都是可移动,但不可拷贝。
  • 执行线程的所有权可以在 std::thread 实例中移动,下面将展示一个例子。例子中,创建了两个执行线程,并且在 std::thread 实例之间(t1,t2和t3)转移所有权。
void some_function();
void some_other_function();
std::thread t1(some_function); // 1 新线程与t1关联
std::thread t2=std::move(t1); // 2 t1的所有权转到t2。执行some_function的线程与t2关联,t1不关联任何线程
t1=std::thread(some_other_function); // 3 临时thread对象创建线程并与t1关联
std::thread t3; // 4 t3默认构造函数创建,没有任何执行线程与t3关联
t3=std::move(t2); // 5 t2的所有权转到t3。
t1=std::move(t3); // 6 赋值操作将使程序崩溃。t1已经关联了一个线程,不能再次关联另外的线程。

函数返回thread对象

std::thread f()
{
void some_function();
return std::thread(some_function);
} 

std::thread g()
{
void some_other_function(int);
std::thread t(some_other_function,42);
return t;
}
thread作为参数传递
void f(std::thread t);
void g()
{
void some_function();
f(std::thread(some_function));
std::thread t(some_function);
f(std::move(t));
}
多个线程并等待结束
#include <iostream>
#include <vector>
#include <thread>
#include <random>
#include <chrono>

using namespace std;

//产生随机数
default_random_engine e;
int randn(int start, int end)
{
    uniform_int_distribution<int> u(start, end);
    return u(e);
}
//让线程随机sleep 0-9s
void do_work(unsigned id)
{
    cout << "id:" << id << " start..." << endl;
    std::this_thread::sleep_for(std::chrono::seconds(randn(0, 9)));
    cout << "id:" << id << " end"<< endl;
}


int main()
{
    vector<thread> thds;
    for (unsigned i = 0; i < 10; i++)
    {
        thds.push_back(thread(do_work, i ));
    }
    //等待所有线程结束
    for_each(thds.begin(), thds.end(), mem_fn(&thread::join));

    return 0;
}

运行时决定线程数量

返回硬件支持的线程数目:thread::hardware_concurrency()。注意:可能返回0.

标识线程

类型为:std::thread::id
获取方式

  1. 通过thread对象的get_id()直接获取
  2. 在当前线程中调用this_thread::get_id()

std::thread::id 对象可以自由的拷贝和对比,因为标识符就可以复用。

  • 如果两个对象的 std::thread::id 相等,那它们就是同一个线程,或者都“无线程”。
  • 如果不等,那么就代表了两个不同线程,或者一个有线程,另一没有线程。
  • 可以作为键值存储
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值