第二章 线程管理

2.1 线程管理基础

包含头文件:

#include<iostream>
#include<thread>

一、创建线程:

  1. 利用入口函数直接创建
thread my_thread(my_thread_function);
  1. 将类作为入口函数
class My_thread{
public:
	void operater()() const{
	//do something
	}
};

My_thread f;
thread my_thread(f);
  1. 使用类的成员函数:
class A{
public:
	My_thread_function(){}
};

A a;
thread my_thread(&A::My_thread_function,&a);
  1. 使用lambda表达式:
std::thread my_thread([]{
  do_something();
  do_something_else();
});

一旦创建了线程,线程立即执行;故而还需设定子线程和主线程的关系(join or detach?);
若主线程不等待子线程(detach),则需要确保在子线程结束前,可访问的数据得有效性。

二、 detach 和 join

  • detach(不等待模式):
my_thread.detach();  
  • 主线程先于子线程结束,使得子线程中某些变量失去意义;

  • 解决方案:使线程函数的功能齐全,将数据复制到线程中,而非复制到共享数据中

  • join(等待模式):
    my_thread.join()

  • 主线程等待子线程结束后,才继续执行;确保主线程不会先于子线程结束。

  • 异常等待:当在线程运行之后产生异常,在join()调用之前抛出,就意味着这次调用会被跳过。当倾向于在无异常的情况下使用join()时,需要在异常处理过程中调用join(),从而避免生命周期的问题。

  • 例子:

struct func; // 定义在清单2.1中
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
}

说明:
try/catch能够捕获轻量级错误;
当函数正常退出时,会执行到2处;当函数执行过程中抛出异常,程序会执行到1处。

2.2 向线程函数传递参数

thread my_thread(my_thread_function, function_param);
  1. 若后续使用detach,则需要特别注意输入的参数:千万不可隐式的转化类型,可能在转化成功前(未给线程)就发生崩溃。
void f(int i,std::string const& s);
void not_oops(int some_param)
{
  char buffer[1024];
  sprintf(buffer,"%i",some_param);
  std::thread t(f,3,std::string(buffer));  // 使用std::string,避免悬垂指针
  t.detach();
}
  1. 若传递引用,需要使用 std::ref(some_param),否则会全部复制:
thread my_thread(my_thread_function, ref(my_param));

2.3 转移线程所有权

假设要写一个在后台启动线程的函数,想通过新线程返回的所有权去调用这个函数,而不是等待线程结束再去调用;或完全与之相反的想法:创建一个线程,并在函数中转移所有权,都必须要等待线程结束。总之,新线程的所有权都需要转移。

std::ifstream, std::unique_ptr 还有std::thread 都是可移动,但不可拷贝。

void some_function();
void some_other_function();
std::thread t1(some_function);          
std::thread t2=std::move(t1);          // t1的所有权就转移给了t2。之后,t1和执行线程已经没有关联了
t1=std::thread(some_other_function);   // 与一个临时std::thread对象相关的线程启动
std::thread t3;                        // 创建但与任何执行线程都没有关联
t3=std::move(t2);                      // t2的所有权转移给t3
t1=std::move(t3);                      //  赋值操作将使程序崩溃

最后一个移动操作,将some_function线程的所有权转移给t1。不过,t1已经有了一个关联的线程(执行some_other_function的线程),所以这里系统直接调用 std::terminate() 终止程序继续运行。

实例:量产线程,等待它们结束

void do_work(unsigned id);

void f()
{
  std::vector<std::thread> threads;
  for(unsigned i=0; i < 20; ++i)
  {
    threads.push_back(std::thread(do_work,i)); // 产生线程
  } 
  std::for_each(threads.begin(),threads.end(),
                  std::mem_fn(&std::thread::join)); // 对每个线程调用join()
}

2.4 运行时决定线程数量

  • std::thread::hardware_concurrency() 这个函数将返回能同时并发在一个程序中的线程数量。例如,多核系统中,返回值可以是CPU核芯的数量。返回值也仅仅是一个提示,当系统信息无法获取时,函数也会返回0。但是,这也无法掩盖这个函数对启动线程数量的帮助。

2.5 识别线程

  • std::this_thread::get_id(); 可得到当前线程的id;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值