《C++并发编程实战》笔记---第2章 线程管理

主要内容

  • 启动新线程
  • 等待线程与分离线程
  • 线程唯一标识符

2.1.1 线程启动

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

1.传入函数

#include <thread>
void function();
std::thread my_thread(function);

2.传入类对象

#include <thread>
class background_task
{ 
public:
    void operator()() const
    {
    do_something();
    do_something_else();
    }
};
background_task f;
std::thread my_thread(f);

  代码中, 提供的函数对象会复制到新线程的存储空间当中, 函数对象的执行和调用都在线程
的内存空间中进行。 函数对象的副本应与原始函数对象保持一致, 否则得到的结果会与我们
的期望不同。

3.传入临时对象

   编译器会将他定义为声明一个my_thread的函数,函数有一个参数(函数指针指向没有参数并返回background_task对象的函数),返回一个std::thread对象的函数。

std::thread my_thread(background_task());//传递一个临时变量,编译器会将解析为函数声明。
/解决方法//
std::thread my_thread((background_task()));
std::thread my_thread{background_task()};

2.1.2 join(等待线程完成)
 

void loop1() 
{
    for(int i = 0; i < 20; i++)
    {
        std::cout << 1 << std::endl;
    }
}

void loop2()
{
    for (int i = 0; i < 20; i++)
    {
        std::cout << 2 << std::endl;
    }
}

void callback() 
{
    std::thread loop1_function(loop1);
    std::thread loop2_function(loop2);
    loop2_function.join();
    loop1_function.join();
    for(int i = 0; i < 20; i++)
    {
        std::cout << 3 << std::endl;
    }
}

int main() 
{
    callback();
    return 0;
}

原始线程等待启动的子线程结束工作,然后开始工作。

执行结果:1和2交替输出,3最后输出。

线程等待可以确保局部变量在线程完成时,才被摧毁。

一个线程只能使用一次join(),一旦使用过一次join(),std::thread对象就不能再次加入,当对其使用joinable()时,将返回false。

2.1.3 特殊情况下的等待

声明线程后没有直接join(),想在其他方法中调用线程的join();

Q:线程运行之后产生异常,在join()调用之前抛出,join()将不会被调用。

A1: 需要再异常处理中调用join()try只能捕获轻量级错误

struct func; // 一个循环方法
void f()
{
    int some_local_state=0;
    func my_func(some_local_state);
    std::thread t(my_func);
    try
    {
        do_something_in_current_thread();
    } 
    catch(...)
    {
        t.join(); // 1
        throw;
    } 
    t.join(); // 2
}

A2:析构函数中使用join()

class thread_guard
{
    std::thread& t;
public:
    explicit thread_guard(std::thread& t_):
    t(t_){}
~thread_guard()
{
    if(t.joinable()) // 1
    {
        t.join(); // 2
    }
} 
    thread_guard(thread_guard const&)=delete; // 3
    thread_guard& operator=(thread_guard const&)=delete;
};
struct func; // 定义在清单2.1中
void f()
{
    int some_local_state=0;
    func my_func(some_local_state);
    std::thread t(my_func);
    thread_guard g(t);
    do_something_in_current_thread();
} // 4

2.1.4 detach 后台运行线程

detach分离后的线程会在后台运行,主线程不会等待这个线程结束。

应用场景:后台监视文件系统, 对缓存进行清理, 亦或对数据结构进行优化

使用条件:

struct func(){}
std::thread t1(func);
if( t1.joinable() )//若返回true,表明函数可以加入线程
{
    t1.detach();
}

2.2 向线程传递参数

1. 传递对象和成员函数

class x
{
public:
    void do_something();
}

x my_x;
std::thread t(&x::do_something, &my_x);//传递对象

x my_x1;
int num(0);
std::thread t(&x::do_something, &my_x, num);//传递对象及成员函数

2. 传递指向动态变量的指针(注意)

void f(int i, std::string const& s);
void oops(int some_param)
{
    char buffer[1024];
    sprintf(buffer, "%i", somr_param);
    std::thread t(f, 3, buffer);//很大可能会在字面值转为std::thread对象前崩溃
    t.detach();
}

key question: std::thread的构造函数会复制提供的变量,就只复制了没有转换成期望类型的字符串字面值。

解决办法:

std::thread t(f,3,std::string(buffer));

3. 传递一个引用

void upadte_data_for_widget(widget_id w, widget_data& data);
void oops_again(widget_id w)
{
    widget_data data;
    std::thread t(updata_data_for_widget, w, data);
    display_status();
    t.join();
    process_widget_data(data);
}

key question : 传递给函数的参数是data变量内部的拷贝引用,而非数据本身的引用,内部拷贝数据将会在数据更新阶段被销毁。

换成这一句将会解决

std::thread t(update_data_for_widget,w ,std::ref(data));

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值