Consider the official boost beast websocket server sync example
Specifically, this part:
for(;;)
{
// This buffer will hold the incoming message
beast::flat_buffer buffer;
// Read a message
ws.read(buffer);
// Echo the message back
ws.text(ws.got_text());
ws.write(buffer.data());
}
To simplify the scenario, let's assume it only ever writes, and the data being written is different each time.
for(;;)
{
// assume some data has been prepared elsewhere, in str
mywrite(str);
}
...
void mywrite(char* str)
{
net::const_buffer b(str, strlen(str));
ws.write(b);
}
This should be fine, as all calls to mywrite
happen sequentially.
What if we had multiple threads and the same for
loop? i.e. what if we had concurrent calls to mywrite
, and to ws.write
by extension? Would something like a strand
or a mutex
be needed?
In other words, do we need to explicitly handle concurrency when calling ws.write
from multiple threads?
I've not yet understood the docs, as they mention:
Thread Safety
Distinctobjects:Safe.
Sharedobjects:Unsafe. The application must also ensure that all asynchronous operations are performed within the same implicit or explicit strand.
And then
Alternatively, for a single-threaded or synchronous application you may write:
websocket::stream<tcp_stream> ws(ioc);
This seems to imply ws
object is not thread-safe, but also for the specific case of a sync app, there's no explicit strand being constructed, implying it's OK?
I was not able to work it out by reading the example, or the websocket implementation. I've never worked with asio
before.
I tried to test it as follows, it didn't seem to fail on my laptop but I don't have the guarantee this will work for other cases. I'm not sure it's a valid test for the case I've described either.
auto lt = [&](unsigned long long i)
{
char s[1000] = {0};
for(;; i)
{
sprintf(s, "Hello from thread:%llu", i);
mywrite(s,30);
}
};
std::thread(lt, 10000000u).detach();
std::thread(lt, 20000000u).detach();
std::thread(lt, 30000000u).detach();
// ws client init, as the official example
for (int i = 0; i < 100; i)
{
beast::flat_buffer buffer;
// Read a message into our buffer
ws.read(buffer);
// The make_printable() function helps print a ConstBufferSequence
std::cout << beast::make_printable(buffer.data()) << std::endl;
}
CodePudding user response:
Yes, you need synchronization because you access the object from multiple threads.
The documentation you quoted is very clear on that:
Sharedobjects:Unsafe [...]
On your rationale for being confused:
This seems to imply ws object is not thread-safe, but also for the specific case of a sync app, there's no explicit strand being constructed, implying it's OK?
It's okay because it's single-threaded, not because it's synchronous. In fact, even if single-threading you still need a strand to prevent overlapped asynchronous write operations. That's what the second part hints at:
[...] The application must also ensure that all asynchronous operations are performed within the same implicit or explicit strand.
Now, the missing piece that might solve the puzzle for you is the example has an implicit logical strand (a sequential chain of non-overlapping asynchronous operations). See also Why do I need strand per connection when using boost::asio?