C ++ coroutine and Network Programming

Source: Internet
Author: User

A coroutine is a collaborative program. The idea is that a series of mutually dependent coroutines use the CPU in sequence, with only one coroutine working at a time, while other coroutine is in sleep state. The coroutine can pause execution at a certain point during running and resume execution from the paused point during running. Coroutine has been proved to be a very useful program component. It is not only widely used by python, lua, ruby and other scripting languages, it is also adopted as the basic unit of concurrency by the next generation of multi-core programming languages such as golang rust-lang. Coroutine can be considered as a user space thread. Compared with the traditional thread, coroutine has two main advantages: different from the thread, the coroutine voluntarily gives out the CPU, and deliver the desired next coroutine operation, rather than being interrupted by System Scheduling at any time. Therefore, the use of coroutine is clearer and easier to understand, and the lock mechanism is not required in most cases. Compared with threads, coroutine switching is controlled by the program and occurs in the user space rather than the kernel space. Therefore, the switching cost is very small. The Network Programming Model www.2cto.com is the first to briefly review some common network programming models. Network Programming models can be divided into two types: Synchronous model and asynchronous model. Synchronous model: the synchronous model uses the blocking IO mode. When calling IO functions such as read in the blocking IO mode, the thread will be blocked until IO is completed or fails. A typical example of the Synchronization Model is the thread per connection model. Every time the accept call on the main thread is blocked, a new thread is created to serve the read/write of the new socket. The advantage of this model is that the program is simple and easy to write. The disadvantage is that the scalability is limited by the number of threads. When there are more and more connections, there are more threads, and frequent thread switching will seriously drag down the performance. Asynchronous model: the asynchronous model generally uses non-blocking IO mode and works with multiple multiplexing mechanisms such as epoll, select, and poll. In non-blocking mode, read is called. If no data is readable, the system returns immediately and notifies the user not to be readable (EAGAIN/EWOULDBLOCK), rather than blocking the current thread. The asynchronous model enables a thread to serve multiple IO objects at the same time. A typical example of an asynchronous model is the reactor model. In the reactor model, we register all the IO events to be processed to a central IO multiplexing (usually epoll/select/poll ), at the same time, the main thread is blocked on the multiplexing. Once an I/O event arrives or is ready, the multiplexing returns and distributes the corresponding I/O events to the corresponding processor (that is, the callback function, finally, the processor calls the read/write function to perform IO operations. The asynchronous model features much better performance and scalability than the Synchronization Model, but its structure is complex and not easy to compile and maintain. In the asynchronous model, the code before IO (the submitter of the IO task) is separated from the code after IO (the callback function. Coroutine and network programming coroutine provide the possibility to overcome the shortcomings of the synchronous and asynchronous models and combine their advantages: Now we assume we have three coroutine A, B, C requires several IO operations. The three threads run in the context of the same scheduler or thread and use the CPU in sequence. The scheduler maintains a multiplexing (epoll/select/poll) within it ). Coroutine A runs first. When it executes an IO operation but the IO operation is not ready immediately, A registers the IO event to the scheduler and voluntarily discards the CPU. In this case, the scheduler switches B to the CPU to start execution. Similarly, when it encounters an IO operation, it registers the IO event to the scheduler and voluntarily abandons the CPU. The scheduler switches C to the cpu to start execution. When all coroutines are blocked, the scheduler checks whether the registered IO events occur or are ready. Assuming that the IO time registered by coroutine B is ready, the scheduler will resume the execution of B, and B will run down from the last place where the CPU was abandoned. A and C are the same. In this way, each coroutine is a synchronous model, but it is an asynchronous model for the entire application. Now, let's take a look at the actual example, echo server. In this example, we will use the orchid library to compile an echo server. The orchid library is a coroutine/network I/O library built on boost. Echo server must first process connection events. We create a coroutine to specifically handle Connection events: typedef boost: shared_ptr <orchid: socket> socket_ptr; // The coroutine void handle_accept (orchid: coroutine_handle co) that handles the ACCEPT event {try {orchid: acceptor (co-> get_schedtor (). get_io_service (); // construct an acceptor. bind_and_listen ("5678", true); for (;) {socket_ptr sock (new orchid: socket (co-> get_scheduler (). get_io_service (); acceptor. accept (* sock, co); // Create a coroutine on the scheduler to serve the new socket. The first parameter is the main function of the coroutine to be created, and the second parameter is the stack size of the coroutine to be created. Co-> get_scheduler (). spawn (boost: bind (handle_io, _ 1, sock), orchid: minimum_stack_size () ;}} catch (boost: system: system_error & e) {cerr <e. code () <"" <e. what () <endl ;}} in orchid, the main function of the coroutine must meet the void (orchid: coroutine_handle) of the function signature, as shown in handle_accept. The co parameter is the coroutine handle, represents the coroutine where the current function is located. In the code above, we created an acceptor and asked it to listen to port 5678, and then waited for the connection to arrive when the connection event arrived, create a new coroutine to serve the new socket. The coroutine for processing socket io is as follows: // coroutine void handle_io (orchid: coroutine_handle co, socket_ptr sock) {orchid: tcp_ostream out (* sock, co) for processing socket io events ); orchid: tcp_istream in (* sock, co); for (std: string str; std: getline (in, str) & out ;) {out <str <endl ;}} IO processing coroutine first creates an input stream and an output stream on the incoming socket, representing the input and output of TCP respectively. Then, read a row from the input stream and output it to the output stream. When the TCP connection on the socket is disconnected, the eof sign of the input stream and output stream is set to be set. Therefore, the loop ends and the coroutine exits. Orchid allows you to operate sockets in the form of a stream. The input stream and output stream provide interfaces std: istream and std: ostream respectively. The input stream and output stream are buffered. If you need a buffer-free read/write socket or user-created buffer, you can directly call the read and write Functions of orchid: socket. However, note that the two functions will throw a boost: system_error exception to indicate an error. Careful readers may have discovered that the handle_io function signature does not meet void (orchid: coroutine_handle). Return to handle_accept and we can find that we actually use boost. bind adapts the handle _ io function to meet the function signature requirements. Www.2cto.com is the final main function: int main () {orchid: schedsche sche; sche. spawn (handle_accept, orchid: coroutine: minimum_stack_size (); // create a coroutine. run ();} in the preceding echo server example, we adopt a coroutine per connection programming model, which is as concise and clear as the traditional thread per connection model, however, the entire program actually runs in the same thread. Because the overhead of coroutine switching is far less than the thread, we can easily start thousands of coroutines at the same time to serve thousands of connections at the same time, which is hard to achieve in the thread per connection model; in terms of performance, the entire underlying IO system actually uses boost. asio is implemented by a high-performance asynchronous io library. In addition, the overhead of coroutine switching is negligible compared with the time spent by I/O.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.