这里需要内存栅吗?

3
考虑以下代码(摘自 Simple-Web-Server,但不需要了解该库即可回答这个问题):
HttpServer server;
thread server_thread;

server.config.port = 8080;
server.default_resource["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
    string content = "Hello world!"
    *response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.size() << "\r\n\r\n" << content;
};

server_thread = thread([&server]() {
    server.start();
});

HttpServer::default_resource是一个std::unordered_map,据我所知,它不是线程安全的。 port是一个无符号短整型。

假设我对C++内存屏障的理解是正确的,那么在新线程中看到的server可能不处于有效状态,因为主线程可能还没有将portdefault_resource的更改写入到其他线程可以访问的内存中。因此,server.start()可能无法正常工作。

要修复这个问题,我需要通过添加atomic_thread_fence来更改代码:

HttpServer server;
thread server_thread;

server.config.port = 8080;
server.default_resource["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
    string content = "Hello world!"
    *response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.size() << "\r\n\r\n" << content;
};

atomic_thread_fence(memory_order_release);

server_thread = thread([&server]() {
    atomic_thread_fence(memory_order_acquire);
    server.start();
});

我的理解正确吗?这两个 atomic_thread_fence 都是必须的吗?


3
在创建一个新线程之前,父线程执行的所有操作在新线程初始化时必须从新线程的角度来看是完整的。因此,在调用std::thread::thread()之前,由于服务器已经被初始化,所以不需要使用线程屏障。 - Thomas Russell
你似乎将实际线程(并发概念)与线程处理器对象(C++对象)混淆了。 - Kerrek SB
1个回答

8

30.3.1.2 thread constructors

template <class F, class ...Args> 
explicit thread(F&& f, Args&&... args);

Synchronization: The completion of the invocation of the constructor synchronizes with the beginning of the invocation of the copy of f.

换句话说:当线程函数被调用时,它与父线程中发生的所有事情同步,直到在父线程中构造 std::thread 为止。
这种类型的显式内存屏障/栅栏不需要。

难道 OP 的代码之所以能够工作,不是因为 server 被传递为参数,并且在那个时候所有影响该变量的副作用的先前代码都必须已经执行了吗?例如,假设 server 不是作为参数传递的,而是全局变量 - 如果我理解正确,那么这将导致同步问题。引用的文本仅表示线程构造函数必须在线程回调之前执行。 - Lundin
即使使用pthread_create()而不是std::thread,也不需要内存栅栏。经验法则是:使用互斥锁或其他同步原语创建线程或等待另一个线程会隐式地发出所需的栅栏。您不必自己这样做。不幸的是,我无法为此提供证明链接。 - Joseph Artsimovich
加入线程时是否相同,即在调用thread::join()后不需要显式的栅栏? - Bernard
1
@Bernard:是的,加入线程隐式地发出必要的屏障,以确保该线程的内存存储对执行加入操作的线程可见。 - Joseph Artsimovich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接