Are memory fences required here?

349 views Asked by At

Consider this code (extracted from Simple-Web-Server, but knowledge of the library shouldn't be necessary to answer this question):

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 is a std::unordered_map, which, to my understanding, isn't thread-safe. port is an unsigned short.

Assuming my understanding of C++ memory fences is correct, server, as seen by the new thread, might not be in a valid state as the main thread might not have written the changes to port and default_resource to memory accessible from other threads. As such, server.start() might not work properly.

To fix this, I would have to change the code by adding to atomic_thread_fences:

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

Is my understanding correct, and are both the atomic_thread_fences necessary?

1

There are 1 answers

4
Sam Varshavchik On BEST ANSWER

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.

In other words: when the thread function gets invoked, it is synchronized with everything that happened in the parent thread up until std::thread gets constructed, in the parent thread.

No explicit memory barriers/fences, of this kind, are needed.