Event-driven model coroutines: Switch when IO operations are encountered. But when do you cut back? How do I know I/O is finished? Many programmers might consider using “thread pooling” or “connection pooling.” Thread pooling is designed to reduce the frequency of thread creation and destruction, maintain a reasonable number of threads, and allow idle threads to resume new execution tasks. Connection pooling maintains a cache pool of connections, reusing existing connections as much as possible and reducing the frequency of connection creation and closure. Both techniques can reduce overhead and are widely used in large systems such as websphere, Tomcat, and various databases. However, “thread pool” and “connection pool” technologies only partially alleviate the resource footprint of frequent IO interface calls. Moreover, the pool always has an upper limit, and when requests go well beyond that limit, the pool system does not respond much better to the outside world than it would without it. So using a pool must take into account the size of the response it faces and adjust the size of the pool based on the response size. “Thread pooling” or “connection pooling” may alleviate some of the stress, but not all of it, as in the example above, where thousands or even tens of thousands of simultaneous client requests may occur. In a word, multi-threaded model can solve small-scale service requests conveniently and efficiently, but in the face of large-scale service requests, multi-threaded model will also encounter bottlenecks, we can use non-blocking interfaces to try to solve this problem

Traditional programming linear model is as follows: start – > code block A – > code block B – > code block C – > – > code block D… Inside each block of code is code that does all sorts of things, but the programmer knows that blocks A,B,C,D… The only thing that can change the flow is data. Enter different data, and depending on the conditional statement, the flow might be A– >C– >E… — – > end. The program may run in a different order each time, but its control flow is determined by the input data and the program you write. If you know the current state of the program (both the input data and the program itself), then you know what to do next, even through the end of the program. For the event-driven program model, the process is roughly as follows: Start > initialize > Wait Unlike the above traditional programming model, the event driver starts, and waits there, waiting for what? Waiting to be triggered by an event. There are also “waiting” times in traditional programming, such as in block D, where you define an input() that requires the user to enter data. But this is different from a traditional programming “wait” like input(), where you, as a programmer, know or force the user to type something, maybe a number, maybe a file name, and if the user makes a mistake, you need to remind him or ask him to retype it. The event-driver waits without knowing at all, and does not force the user to input or do anything. As soon as an event occurs, the program “reacts” accordingly. These events include: input information, mouse, hitting a key on a keyboard and triggering an internal timer.

In general, when we write programs for servers to handle models, we have the following models:

(1) Each time a request is received, create a new process to process the request; (2) Each time a request is received, a new thread is created to process the request; (3) Each time a request is received, put a list of events into the main process to process the request through non-blocking I/OCopy the code

The third is the coroutine, event-driven approach, and it is generally accepted that the (3) approach is the approach adopted by most web servers

On event-driven models

<! DOCTYPE html> <html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Title</title>

</head>
<body>
<p onclick="fun()"</p> <scripttype="text/javascript">
   function fun() {
         alert('about? ')
   }
</script>
</body>
</html>
Copy the code

Event driven mouse click event registration

In UI programming, mouse clicks are often matched. How do you get mouse clicks in the first place? There are two methods: 1. This method has the following disadvantages: WASTE of CPU resources. The frequency of mouse clicks may be very small, but the scanning thread will still cycle detection, which will cause a lot of WASTE of CPU resources; What if the interface for scanning mouse clicks is blocked? If it is blocked, there will be the following problem, if we have to scan not only the mouse click, but also the keyboard down, because the mouse scan is blocked, then we may never scan the keyboard; If a loop has a large number of devices to scan, this can lead to response time issues. So, this approach is very bad.

Most UI programming is event-driven. For example, many UI platforms provide an onClick() event, which represents the mouse-press event. The general idea of the event-driven model is as follows: there is an event (message) queue; Add a click event (message) to the queue when the mouse is pressed; There is a loop that keeps pulling events from the queue, calling different functions for different events, such as onClick(), onKeyDown(), etc. Events (messages) typically hold their own handler Pointers, so that each message has its own handler;

Event-driven programming is a programming paradigm in which the execution flow of a program is determined by external events. It features an event loop that uses a callback mechanism to trigger processing when an external event occurs. Two other common programming paradigms are synchronous (single-threaded) and multithreaded programming. Let’s compare and contrast single-threaded, multi-threaded, and event-driven programming models with examples. The diagram below shows what the program does over time in these three modes. The program has three tasks to complete, each of which blocks itself while waiting for I/O operations. The time spent blocking I/O operations is indicated in a gray box.

User-space and kernel-space processes switch processes blocking file descriptor cache I/OCopy the code

Today’s operating systems use virtual storage, so for a 32-bit operating system, its addressing space (virtual storage space) is 4G (2 ^ 32). The core of the operating system is the kernel, which is independent of ordinary applications and has access to the protected memory space as well as all permissions to access the underlying hardware devices. In order to ensure that the user process cannot operate the kernel directly and ensure the security of the kernel, the system divides the virtual space into two parts, one is the kernel space and the other is the user space. For Linux, the highest 1G byte (from virtual addresses 0xC0000000 to 0xFFFFFFFF) is used by the kernel and is called kernel space, while the lower 3G byte (from virtual addresses 0x00000000 to 0xBfffff) is used by various processes and is called user space

Process switching In order to control process execution, the kernel must be able to suspend a process running on the CPU and resume execution of a previously suspended process. This behavior is called process switching, and this switching is done by the operating system. Therefore, it can be said that any process runs under the support of the operating system kernel and is closely related to the kernel. Transitions from running one process to running another with the following changes: Saves the processor context, including program counters and other registers. Update PCB information. Move the process PCB to the appropriate queue, such as ready, blocked at an event queue, etc. Select another process to execute and update its PCB. Update memory management data structures. Restore processor context. Note: All in all, it’s resource intensive

The system automatically executes a Block primitive to change the running state to the blocked state because some expected events do not occur, such as system resource request failure, waiting for the completion of an operation, new data arrival, or no new work to be done. It can be seen that the blocking of a process is an active behavior of the process itself, so only the running process (to obtain CPU) can be put into the blocking state. When a process is blocked, it consumes no CPU resources.

File descriptor FD File descriptor is a computer science term used to describe an abstract concept used to refer to a File. The file descriptor is formally a non-negative integer. In fact, it is an index value that points to the record table of open files that the kernel maintains for each process. When a program opens an existing file or creates a new file, the kernel returns a file descriptor to the process. In programming, some low-level programming tends to revolve around file descriptors. However, the concept of file descriptors is usually only applicable to operating systems such as UNIX and Linux.

Cache I/O Cache I/O is also known as standard I/O. The default I/O operation for most file systems is cache I/O. In Linux’s cached I/O mechanism, the operating system caches I/O data in the file system’s page cache. That is, data is copied to the operating system kernel buffer before it is copied from the operating system kernel buffer to the application address space. If user space cannot access the kernel space directly, the data must be copied to user memory first. Disadvantages of cache I/O: Data needs to be copied in the application address space and the kernel for many times during data transmission. These data copying operations bring high CPU and memory overhead. What are synchronous IO and asynchronous IO, blocking IO and non-blocking IO respectively, what is the difference? The answer to this question may vary from person to person, some people think that asynchronous IO and non-blocking IO are the same thing. This is actually because different people have different knowledge backgrounds, and the context when discussing this issue is also different. So, to better answer this question, let me limit the context of this article. The background of this article is Network IO in Linux. Five IO Models:

blocking IO
    nonblocking IO
    IO multiplexing
    signal driven IO
    asynchronous IO
Copy the code

Since Signal Driven IO is not commonly used in practice, I will only mention the remaining four IO models. Repeat the objects and steps involved when I/O occurs. A network IO (for example, read) involves two system objects: the process (or thread) that calls the IO, and the kernel. When a read operation occurs, it goes through two phases:

1 Waiting for data to be readyforIt's important to remember that Copying the data from the kernel to the process Because the difference between these IO models is that there are different situations in the two phases.Copy the code

Due to the long space, the next article will introduce each IO in detail

Identify the QR code in the figure and get the full set of Python video materials