Using io_service for synchronous tcp accept + custom tasks
I've written code of the form: Code: io_service svc; io_service::work work(svc); thread t1( [&svc]() { svc.run(); } ); thread t2( [&svc]() { svc.run(); } ); thread t3( [&svc]() { svc.run(); } ); endpoint ep(ip::tcp::v4(), port); acceptor acceptor(svc, ep); while (true) { shared_ptr<socket> sock(new socket(svc)); acceptor.accept(*sock); svc.post( [sock]() { /* do stuff on sock here */ }); } Is this way of using io_service for accepting tcp connections and also as a thread pool for serving connected clients valid or could I hit some undefined behavior. -- Aaron Levy aaron.levy@yandex.com
01.02.2015, 19:11, "Aaron Levy"
I've written code of the form:
Code:
io_service svc; io_service::work work(svc);
thread t1( [&svc]() { svc.run(); } ); thread t2( [&svc]() { svc.run(); } ); thread t3( [&svc]() { svc.run(); } );
endpoint ep(ip::tcp::v4(), port); acceptor acceptor(svc, ep);
while (true) { shared_ptr<socket> sock(new socket(svc)); acceptor.accept(*sock);
svc.post( [sock]() { /* do stuff on sock here */ }); }
Is this way of using io_service for accepting tcp connections and also as a thread pool for serving connected clients valid or could I hit some undefined behavior.
--
Adding an appropriate library tag to the subject line. -- Aaron Levy aaron.levy@yandex.com
On 2/02/2015 02:52, Aaron Levy wrote:
io_service svc; io_service::work work(svc);
thread t1( [&svc]() { svc.run(); } ); thread t2( [&svc]() { svc.run(); } ); thread t3( [&svc]() { svc.run(); } );
endpoint ep(ip::tcp::v4(), port); acceptor acceptor(svc, ep);
while (true) { shared_ptr<socket> sock(new socket(svc)); acceptor.accept(*sock);
svc.post( [sock]() { /* do stuff on sock here */ }); }
Is this way of using io_service for accepting tcp connections and also as a thread pool for serving connected clients valid or could I hit some undefined behavior.
A little of both. In general you can post whatever jobs you like to an io_service (including things that aren't I/O -- it's a great generic thread pool), but when multiple threads are running the service any one of those threads can end up running the job / handling the callback. Most of the io objects (eg. sockets), and indeed most other objects, are not intended for a single instance to be used concurrently from multiple threads. You can prevent this either by ensuring that only a single operation is "in flight" on a single object at a time (implicit strands) or that operations on the same object are explicitly synchronised via a strand object, or using some other mechanism (eg. locks), although the latter is less preferred. In the code above, you should be fine with regard to acceptor vs. sock, since you're only playing with one at a time. But you'll need to be careful if doing multiple operations on sock. Also, I could be wrong about this, but I think if you eg. perform a blocking read inside your sock job it will tie up a whole thread for the duration, which means that you will quickly run out if you get multiple connections. Using async code should avoid this.
04.02.2015, 06:22, "Gavin Lambert"
On 2/02/2015 02:52, Aaron Levy wrote:
io_service svc; io_service::work work(svc);
thread t1( [&svc]() { svc.run(); } ); thread t2( [&svc]() { svc.run(); } ); thread t3( [&svc]() { svc.run(); } );
endpoint ep(ip::tcp::v4(), port); acceptor acceptor(svc, ep);
while (true) { shared_ptr<socket> sock(new socket(svc)); acceptor.accept(*sock);
svc.post( [sock]() { /* do stuff on sock here */ }); }
Is this way of using io_service for accepting tcp connections and also as a thread pool for serving connected clients valid or could I hit some undefined behavior.
A little of both. In general you can post whatever jobs you like to an io_service (including things that aren't I/O -- it's a great generic thread pool), but when multiple threads are running the service any one of those threads can end up running the job / handling the callback.
Most of the io objects (eg. sockets), and indeed most other objects, are not intended for a single instance to be used concurrently from multiple threads. You can prevent this either by ensuring that only a single operation is "in flight" on a single object at a time (implicit strands) or that operations on the same object are explicitly synchronised via a strand object, or using some other mechanism (eg. locks), although the latter is less preferred.
In the code above, you should be fine with regard to acceptor vs. sock, since you're only playing with one at a time. But you'll need to be careful if doing multiple operations on sock.
Also, I could be wrong about this, but I think if you eg. perform a blocking read inside your sock job it will tie up a whole thread for the duration, which means that you will quickly run out if you get multiple connections. Using async code should avoid this.
Consider an alternative to my example while still using sync I/O. io_service svc; endpoint ep(ip::tcp::v4(), port); acceptor acceptor(svc, ep); boost::thread_group group; while (true) { shared_ptr<socket> sock(new socket(svc)); acceptor.accept(*sock); group.create_thread([sock]() { /* do some processing */ }); } group.join_all(); // we never reach here The problem with the first example would manifest itself with increasing rate of connections - connections would take longer to be accepted and be ready for read / write. With this one, connections would not remain hung up for too long, but subsequent I/O could because there could be too many threads vying for the processors. I'm wondering which one would be preferable. Are there any specific advantages of sync I/O over async I/O other than simple code? -- Aaron Levy aaron.levy@yandex.com
On 02/04/2015 08:38 AM, Aaron Levy wrote:
Consider an alternative to my example while still using sync I/O.
You may consider using coroutines instead (see the spawn examples.) This way you can have one coroutine per request, served by a fixed number of threads. Whenever the coroutine has to wait for more I/O it will yield the thread so that it can be used by other coroutines.
On 4/02/2015 20:38, Aaron Levy wrote:
04.02.2015, 06:22, "Gavin Lambert"
: Also, I could be wrong about this, but I think if you eg. perform a blocking read inside your sock job it will tie up a whole thread for the duration, which means that you will quickly run out if you get multiple connections. Using async code should avoid this.
Consider an alternative to my example while still using sync I/O.
io_service svc;
endpoint ep(ip::tcp::v4(), port); acceptor acceptor(svc, ep);
boost::thread_group group;
while (true) { shared_ptr<socket> sock(new socket(svc)); acceptor.accept(*sock);
group.create_thread([sock]() { /* do some processing */ }); } group.join_all(); // we never reach here
The problem with the first example would manifest itself with increasing rate of connections - connections would take longer to be accepted and be ready for read / write. With this one, connections would not remain hung up for too long, but subsequent I/O could because there could be too many threads vying for the processors. I'm wondering which one would be preferable.
Using a thread per connection is a server antipattern. You can usually get away with it for a surprisingly large number of connections, but it's still highly vulnerable to a DDoS (either as an actual attack or just from becoming too popular).
Are there any specific advantages of sync I/O over async I/O other than simple code?
No, the only advantage of sync I/O is that it's easier for humans to follow. (Well, that's not strictly true; if you do bind a single thread per connection then you can make use of TLS, and a few other esoteric bits like that.) Async code in general performs better (if written properly, which can be trickier than sync code) because threads only wait for work when there is no work to do, not blocking because one particular connection has nothing to do. So you need less threads overall and there are less context switches required when multiple items of work are ready. But the code is a bit more disjointed (although lambdas can help with this). Coroutines are a good compromise between the two -- they read almost like synchronous code but have performance closer to async code.
participants (3)
-
Aaron Levy
-
Bjorn Reese
-
Gavin Lambert