C++ thread management

1. Thread management

Start the thread
std::thread my_thread(background_task());

Suspend thread
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. 新线程可能还在运行

Without waiting for the thread to end, the function in the new thread may still be running. If the thread is still running, it will call the do_something(i) function①, and then it will access the destroyed variable

Wait for the thread to end
my_thread.join()

Using RAII to wait for a thread to complete

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

Second, pass parameters to the thread

daemon thread

Daemon threads (daemon threads), daemon threads in UNIX refer to threads that run in the background without any user interface. The characteristic of this kind of thread is long-running; the life cycle of the thread may start from the beginning to the end of a certain application, it may monitor the file system in the background, and it may also clean up the cache or optimize the data structure

Use a detached thread to process other documents

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

If the user chooses to open a new document, in order to open the document quickly, it is necessary to start a new thread to open the new document ①, and separate the thread ②. The new thread simply opens another file, just as the current thread does. Therefore, the edit_document function can be reused to open a new file by passing parameters.

Note that
std::thread cannot perform type conversion and needs to be explicitly declared

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

This also happens with references to the use of

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
}

The solution is:

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

Other uses:

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

In this code, the new thread uses my_x.do_lengthy_work() as a thread function; the address ① of my_x is provided to the function as a pointer object

You can use std::move to transfer a dynamic object to another thread

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

Specify std::move§ in the constructor of std::thread, the ownership of the big_object object is first transferred to the internal storage of the newly created thread, and then passed to the process_big_object function

3. Transfer thread ownership

1. t1 already has an associated thread, and then using std::move will be an assignment operation, which will cause the program to crash

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 supports movement, which means that the ownership of the thread can be transferred outside the function, just like the following program.

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. The following code upgrades the thread_guard class to support mobile thread operations

Usage of 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. Mass production thread, waiting for the end

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

We often need threads to divide the total workload of an algorithm, so before the algorithm finishes, all threads must finish. The above code shows that the work done by the thread is independent, and the result will only be affected by the shared data.

4. Determine the number of threads at runtime

std::thread::hardware_concurrency() is a useful function in the newer C++ standard library. This function will return the number of threads that can be concurrently in a program.

Native parallel version of 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
}


5. Identify threads

The first type can be obtained directly by calling the member function get_id() of the std::thread object
. The second type can also be obtained by calling std::this_thread::get_id() (this function is defined in the header file) in the current thread. Thread ID
The standard library also provides a std::hash<std::thread::id> container, so std::thread::id can also be used as the key value of an unordered container.

Main thread and child thread work separately

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

Guess you like

Origin blog.csdn.net/weixin_43367756/article/details/126373314