C++线程管理

一、线程管理

启动线程
std::thread my_thread(background_task());

挂起线程
my_thread.detach();

struct func
{
    
    
  int& i;
  func(int& i_) : i(i_) {
    
    }
  void operator() ()
  {
    
    
    for (unsigned j=0 ; j<1000000 ; ++j)
    {
    
    
      do_something(i);           // 1. 潜在访问隐患:悬空引用
    }
  }
};

void oops()
{
    
    
  int some_local_state=0;
  func my_func(some_local_state);
  std::thread my_thread(my_func);
  my_thread.detach();          // 2. 不等待线程结束
}                              // 3. 新线程可能还在运行

不等待线程结束,新线程中的函数可能还在运行,如果线程还在运行,它就会去调用do_something(i)函数①,这时就会访问已经销毁的变量

等待线程结束
my_thread.join()

使用RAII等待线程完成

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

二、向线程传递参数

守护线程

守护线程(daemon threads),UNIX中守护线程是指,且没有任何用户接口,并在后台运行的线程。这种线程的特点就是长时间运行;线程的生命周期可能会从某一个应用起始到结束,可能会在后台监视文件系统,还有可能对缓存进行清理,亦或对数据结构进行优化

使用分离线程去处理其他文档

void edit_document(std::string const& filename)
{
    
    
  open_document_and_display_gui(filename);
  while(!done_editing())
  {
    
    
    user_command cmd=get_user_input();
    if(cmd.type==open_new_document)
    {
    
    
      std::string const new_name=get_filename_from_user();
      std::thread t(edit_document,new_name);  // 1
      t.detach();  // 2
    }
    else
    {
    
    
       process_user_input(cmd);
    }
  }
}

如果用户选择打开一个新文档,为了让迅速打开文档,需要启动一个新线程去打开新文档①,并分离线程②。与当前线程做出的操作一样,新线程只不过是打开另一个文件而已。所以,edit_document函数可以复用,通过传参的形式打开新的文件。

注意
std::thread 无法进行类型转换,需要进行显示声明

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();
}

这种情况也会发生在引用的使用上

void update_data_for_widget(widget_id w,widget_data& data); // 1
void oops_again(widget_id w)
{
    
    
  widget_data data;
  std::thread t(update_data_for_widget,w,data); // 2
  display_status();
  t.join();
  process_widget_data(data); // 3
}

解决方法是:

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

其他用法:

class X
{
    
    
public:
  void do_lengthy_work();
};
X my_x;
std::thread t(&X::do_lengthy_work,&my_x); // 1

这段代码中,新线程将my_x.do_lengthy_work()作为线程函数;my_x的地址①作为指针对象提供给函数

可以使用std::move将一个动态对象转移到另一个线程中去

void process_big_object(std::unique_ptr<big_object>);

std::unique_ptr<big_object> p(new big_object);
p->prepare_data(42);
std::thread t(process_big_object,std::move(p));

在std::thread的构造函数中指定std::move§,big_object对象的所有权就被首先转移到新创建线程的的内部存储中,之后传递给process_big_object函数

三、转移线程所有权

1.t1已有关联线程,再使用std::move将会是赋值操作,会使程序崩溃

void some_function();
void some_other_function();
std::thread t1(some_function);            // 1
std::thread t2=std::move(t1);            // 2
t1=std::thread(some_other_function);    // 3
std::thread t3;                            // 4
t3=std::move(t2);                        // 5
t1=std::move(t3);                        // 6 赋值操作将使程序崩溃

2.std::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;
}

3.以下代码对thread_guard类进行了升级,支持移动线程操作

scoped_thread的用法

class scoped_thread
{
    
    
  std::thread t;
public:
  explicit scoped_thread(std::thread t_):                 // 1
    t(std::move(t_))
  {
    
    
    if(!t.joinable())                                     // 2
      throw std::logic_error(“No thread”);
  }
  ~scoped_thread()
  {
    
    
    t.join();                                            // 3
  }
  scoped_thread(scoped_thread const&)=delete;
  scoped_thread& operator=(scoped_thread const&)=delete;
};

struct func; // 定义在清单2.1中

void f()
{
    
    
  int some_local_state;
  scoped_thread t(std::thread(func(some_local_state)));    // 4
  do_something_in_current_thread();
}                                                        // 5


4.量产线程,等待结束

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()
}

我们经常需要线程去分割一个算法的总工作量,所以在算法结束的之前,所有的线程必须结束。上面代码说明线程所做的工作都是独立的,并且结果仅会受到共享数据的影响。

四、运行时决定线程数量

std::thread::hardware_concurrency()在新版C++标准库中是一个很有用的函数。这个函数将返回能同时并发在一个程序中的线程数量。

原生并行版的std::accumulate

template<typename Iterator,typename T>
struct accumulate_block
{
    
    
  void operator()(Iterator first,Iterator last,T& result)
  {
    
    
    result=std::accumulate(first,last,result);
  }
};

template<typename Iterator,typename T>
T parallel_accumulate(Iterator first,Iterator last,T init)
{
    
    
  unsigned long const length=std::distance(first,last);

  if(!length) // 1 如果输入的范围为空,就会得到init的值。
    return init;

  unsigned long const min_per_thread=25;
  unsigned long const max_threads=
      (length+min_per_thread-1)/min_per_thread; // 2

  unsigned long const hardware_threads=
      std::thread::hardware_concurrency();

  unsigned long const num_threads=  // 3 计算量的最大值和硬件支持线程数中,较小的值为启动线程的数量
      std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads);

  unsigned long const block_size=length/num_threads; // 4

  std::vector<T> results(num_threads);
  std::vector<std::thread> threads(num_threads-1);  // 5

  Iterator block_start=first;
  for(unsigned long i=0; i < (num_threads-1); ++i)
  {
    
    
    Iterator block_end=block_start;
    std::advance(block_end,block_size);  // 6 advance() 函数用于将迭代器前进(或者后退)指定长度的距离,
    threads[i]=std::thread(     // 7
        accumulate_block<Iterator,T>(),
        block_start,block_end,std::ref(results[i]));
    block_start=block_end;  // 8
  }
  accumulate_block<Iterator,T>()(
      block_start,last,results[num_threads-1]); // 9
      
  std::for_each(threads.begin(),threads.end(),
       std::mem_fn(&std::thread::join));  // 10
       /* 函数模板std :: mem_fn生成指向成员的指针的包装对象,该对象可以存储,复制和调用指向成员的指针。 调用std :: mem_fn时,可以使用对象的引用和指针(包括智能指针)。
例子:*/

  return std::accumulate(results.begin(),results.end(),init); // 11
}


五、识别线程

第一种,可以通过调用std::thread对象的成员函数get_id()来直接获取
第二种,当前线程中调用std::this_thread::get_id()(这个函数定义在头文件中)也可以获得线程标识
标准库也提供std::hash< std::thread::id >容器,所以std::thread::id也可以作为无序容器的键值。

主线程与子线程分开工作

std::thread::id master_thread;
void some_core_part_of_algorithm()
{
    
    
  if(std::this_thread::get_id()==master_thread)
  {
    
    
    do_master_thread_work();
  }
  do_common_work();
}

猜你喜欢

转载自blog.csdn.net/weixin_43367756/article/details/126373314