在多线程环境中使用std :: call_once()进行初始化 [英] Initialising with std::call_once() in a multithreading environment

查看:79
本文介绍了在多线程环境中使用std :: call_once()进行初始化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在阅读《第二​​版C ++并发行动》 X .本书包含一个使用 std :: call_once()的示例> 函数模板以及 std :: once_flag 对象以线程安全的方式提供某种惰性初始化.

这是这本书的摘录:

  class X {上市:X(常量connection_details&详细信息):connection_details_ {details}{}无效send_data(const data_packet& data){std :: call_once(connection_init_,& X :: open_connection,this);connection_.send(data);//使用connection_}data_packet receive_data(){std :: call_once(connection_init_,& X :: open_connection,this);返回connection_.recv(data);//使用connection_}私人的:void open_connection(){connection_.open(connection_details_);//connection_已修改}connection_details connection_details_;connection_handle connection_;std :: once_flag connection_init_;}; 

上面的代码的作用是延迟连接的创建,直到客户端想要接收数据或发送数据为止.该连接是由 open_connection()私有成员函数创建的,而不是由 X 的构造函数创建的.构造函数只保存连接详细信息,以便以后可以创建连接.

上面的 open_connection()成员函数仅被调用一次 ,到目前为止效果很好.在单线程上下文中,这将按预期工作.但是,如果多个线程在同一对象上调用 send_data() receive_data()成员函数怎么办?

显然, open_connection()中的 connection _ 数据成员的修改/更新与 send_data()或 receive_data().

std :: call_once()是否阻塞第二个线程,直到第一个线程从 std :: call_once()返回?


X 部分 3.3.1.:在初始化期间保护共享数据

解决方案

基于此发布,我已经创建了此答案.

我想查看 std :: call_once()是否与在同一 std :: once_flag 对象.下面的程序创建了几个线程,这些线程调用一个函数,该函数包含对 std :: call_once()的调用,这会使调用线程长时间处于睡眠状态.

  #include< mutex>std :: once_flag init_flag;std :: mutex mtx; 

init_flag 是要与 std :: call_once()调用一起使用的 std :: once_flag 对象.互斥锁 mtx 只是为了避免从不同线程将字符流传输到 std :: cout 时,在 std :: cout 上的交错输出.

init()函数是由 std :: call_once()调用的函数.它显示文本 initializing ... ,使调用线程休眠三秒钟,然后显示文本 done ,然后返回:

  #include< thread>#include< chrono>#include< iostream>无效的init(){{std :: lock_guard< std :: mutex>lg(mtx);std :: cout<<正在初始化...";}std :: this_thread :: sleep_for(std :: chrono :: seconds {3});{std :: lock_guard< std :: mutex>lg(mtx);std :: cout<<完成"<<'\ n';}} 

此函数的目的是睡眠足够长的时间(在这种情况下为三秒钟),以便其余线程有足够的时间到达 std :: call_once()调用.这样,我们将能够查看它们是否阻塞,直到执行此函数的线程从中返回为止.

do_work()函数由 main()中创建的所有线程调用:

  void do_work(){std :: call_once(init_flag,init);print_thread_id();} 

init()仅由一个线程调用(即,仅被一次调用).所有线程都调用 print_thread_id(),即,它对 main()中创建的每个线程执行一次.

print_thread_id()仅显示当前线程ID:

  void print_thread_id(){std :: lock_guard< std :: mutex>lg(mtx);std :: cout<<std :: this_thread :: get_id()<<'\ n';} 

main()中创建了总共16个线程,它们调用 do_work()函数:

  #include< vector>int main(){std :: vector< std :: thread>线程(16);用于(自动和th:线程)th = std :: thread {do_work};用于(自动和th:线程)th.join();} 

我在系统上得到的输出是:

 正在初始化...完成0x7000054a90000x7000057380000x7000056b50000x7000056320000x7000054260000x70000552c0000x7000055af0000x7000057bb0000x70000583e0000x7000058c10000x7000059c70000x700005a4a0000x7000059440000x700005acd0000x700005b500000x700005bd3000 

此输出表示没有线程执行 print_thread_id(),直到第一个调用 std :: call_once()的线程从中返回为止.这意味着这些线程在 std :: call_once()调用时被阻塞.

I'm reading the book C++ Concurrency in Action, 2nd Edition X. The book contains an example that uses the std::call_once() function template together with an std::once_flag object to provide some kind of lazy initialisation in thread-safe way.

Here a simplified excerpt from the book:

class X {
public:
   X(const connection_details& details): connection_details_{details}
   {}

   void send_data(const data_packet& data) {
      std::call_once(connection_init_, &X::open_connection, this);
      connection_.send(data); // connection_ is used
   }

   data_packet receive_data() {
      std::call_once(connection_init_, &X::open_connection, this);
      return connection_.recv(data); // connection_ is used
   }

private:
   void open_connection() {
      connection_.open(connection_details_); // connection_ is modified
   }

   connection_details connection_details_;
   connection_handle connection_;
   std::once_flag connection_init_;
};

What the code above does, is to delay the creation of the connection until the client wants to receive data or has data to send. The connection is created by the open_connection() private member function, not by the constructor of X. The constructor only saves the connection details to be able to create the connection at some later point.

The open_connection() member function above is called only once, so far so good. In a single-threaded context, this will work as expected. However, what if multiple threads are calling either the send_data() or the receive_data() member function on the same object?

Apparently, the modification/update of the connection_ data member in open_connection() is not synchronised with any of its uses in send_data() or receive_data().

Does std::call_once() block a second thread until the first one returns from std::call_once()?


XSection 3.3.1.: Protecting shared data during initialization

解决方案

Based on this post I've created this answer.

I wanted to see whether std::call_once() synchronises with other calls to std::call_once() on the same std::once_flag object. The following program creates several threads that call a function that contains a call to std::call_once() that puts the calling thread to sleep for long time.

#include <mutex>

std::once_flag init_flag;
std::mutex mtx; 

init_flag is the std::once_flag object to be used with the std::call_once() call. The mutex mtx is just for avoiding interleaved output on std::cout when streaming characters into std::cout from different threads.

The init() function is the one called by std::call_once(). It displays the text initialising..., puts the calling thread to sleep for three seconds and then displays the text done before returning:

#include <thread>
#include <chrono>
#include <iostream>

void init() {
   {
      std::lock_guard<std::mutex> lg(mtx);
      std::cout << "initialising...";
   }

   std::this_thread::sleep_for(std::chrono::seconds{3});  

   {
      std::lock_guard<std::mutex> lg(mtx);
      std::cout << "done" << '\n';
   }
}

The purpose of this function is to sleep for long enough (three seconds in this case), so that the remaining threads have enough time to reach the std::call_once() call. This way we will be able to see whether they block until the thread executing this function returns from it.

The function do_work() is called by all threads that are created in main():

void do_work() {
   std::call_once(init_flag, init);
   print_thread_id(); 
}

init() will be only called by one thread (i.e., it will be called only once). All threads call print_thread_id(), i.e., it is executed once for every thread created in main().

The print_thread_id() simply displays the current thread id:

void print_thread_id() {
   std::lock_guard<std::mutex> lg(mtx);
   std::cout << std::this_thread::get_id() << '\n';
}

A total of 16 threads, which call the do_work() function, are created in main():

#include <vector>

int main() {
   std::vector<std::thread> threads(16);
   for (auto& th: threads)
      th = std::thread{do_work};

   for (auto& th: threads)
      th.join();
}

The output I get on my system is:

initialising...done
0x7000054a9000
0x700005738000
0x7000056b5000
0x700005632000
0x700005426000
0x70000552c000
0x7000055af000
0x7000057bb000
0x70000583e000
0x7000058c1000
0x7000059c7000
0x700005a4a000
0x700005944000
0x700005acd000
0x700005b50000
0x700005bd3000

This output means that no thread executes print_thread_id() until the first thread that called std::call_once() returns from it. This implies that those threads are blocked at the std::call_once() call.

这篇关于在多线程环境中使用std :: call_once()进行初始化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆