当前位置:网站首页>Processes of libuv
Processes of libuv
2022-07-06 09:29:00 【What are you afraid of? The truth is infinite】
libuv Provides quite a number of sub process management functions , And it's cross platform , It's also allowed to use stream, Or say pipe Complete interprocess communication .
stay UNIX There is a consensus in , The process only does one thing , And do it well . therefore , Processes usually accomplish different tasks by creating sub processes ( for example , stay shell Use in pipe).
A multi process , Model of communication through messages , It's better than multithreaded , The model of shared memory is much easier to understand .
At present, a common reason against event driven programming is , It cannot make good use of the advantages of modern multi-core computers . A multithreaded program , The kernel can schedule threads to different cpu Execute in core , To improve performance . But one event-loop Your program has only one thread . actually , Workspaces can be allocated to multiple processes , Each process executes one event-loop, Then each process is assigned to a different cpu Execute in core .
Spawning child processes
One of the simplest uses is , You want to start a process , Then know when it ends . Need to use uv_spawn To complete the task :
spawn/main.c
uv_loop_t *loop;
uv_process_t child_req;
uv_process_options_t options;
int main() {
loop = uv_default_loop();
char* args[3];
args[0] = "mkdir";
args[1] = "test-dir";
args[2] = NULL;
options.exit_cb = on_exit;
options.file = "mkdir";
options.args = args;
int r;
if ((r = uv_spawn(loop, &child_req, &options))) {
fprintf(stderr, "%s\n", uv_strerror(r));
return 1;
} else {
fprintf(stderr, "Launched process with ID %d\n", child_req.pid);
}
return uv_run(loop, UV_RUN_DEFAULT);
}
Note
Because of the above options Global variable , Therefore, it is initialized to 0. If you define options, Please remember to set all useless fields to 0.
uv_process_options_t options = {0};
uv_process_t Just as a handle , All options passed uv_process_options_t Set up , To simply start a process , You just need to set up file and args,file Is the program to be executed ,args Is the required parameter ( and c In language main The incoming parameters of the function are similar ). because uv_spawn Used internally execvp, So there is no need to provide an absolute address . Follow the Convention , The actual number of parameters passed in is one more than the required parameters , Because the last parameter will be set to NULL.
In function uv_spawn After being called ,uv_process_t.pid Will contain child processes id.
Callback function on_exit() When called , Pass in exit State and cause exit The signal of .
spawn/main.c
void on_exit(uv_process_t *req, int64_t exit_status, int term_signal) {
fprintf(stderr, "Process exited with status %" PRId64 ", signal %d\n", exit_status, term_signal);
uv_close((uv_handle_t*) req, NULL);
After the process shuts down , It needs to be recycled handler.
Changing process parameters
Before the subprocess starts executing , You can use uv_process_options_t
Set up the operating environment .
Change execution directory
Set up uv_process_options_t.cwd, Change the corresponding directory .
Set environment variables
uv_process_options_t.env The format is null Is the ending string array , Each of these strings is in the form of VAR=VALUE. These values are used to set the environment variables of the process . If the child process wants to inherit the environment variables of the parent process , will uv_process_options_t.env Set to null.
Option flags
Set by using the bitwise or value identified below uv_process_options_t.flags Value , You can define the behavior of child processes :
UV_PROCESS_SETUID- The execution user of the subprocess id(UID) Set to uv_process_options_t.uid The value in .
UV_PROCESS_SETGID- Set the execution group of the child process id(GID) Set to uv_process_options_t.gid The value in . Only in unix The system's operating system supports setting users id And groups id, stay windows Next setting will fail ,uv_spawn Returns the UV_ENOTSUP.
UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS- stay windows On ,uv_process_options_t.args Don't wrap parameters in quotation marks . This tag pair unix Invalid .
UV_PROCESS_DETACHED- In a new session (session) Start the child process , In this way, the child process can continue after the parent process exits . Please see the following example :
Detaching processes
Use logo UV_PROCESS_DETACHED You can start the daemon (daemon), Or make the child process independent from the parent process , In this way, the exit of the parent process will not affect it .
Use logo UV_PROCESS_DETACHED
You can start the daemon (daemon), Or make the child process independent from the parent process , In this way, the exit of the parent process will not affect it .
detach/main.c
int main() {
loop = uv_default_loop();
char* args[3];
args[0] = "sleep";
args[1] = "100";
args[2] = NULL;
options.exit_cb = NULL;
options.file = "sleep";
options.args = args;
options.flags = UV_PROCESS_DETACHED;
int r;
if ((r = uv_spawn(loop, &child_req, &options))) {
fprintf(stderr, "%s\n", uv_strerror(r));
return 1;
}
fprintf(stderr, "Launched sleep with PID %d\n", child_req.pid);
uv_unref((uv_handle_t*) &child_req);
return uv_run(loop, UV_RUN_DEFAULT);
Remember a little , Namely handle Will always monitor child processes , So your program will not exit .uv_unref()
Will be released handle.
Sending signals to processes
libuv Packed unix The standard kill(2) system call , And in windows A call with similar usage is implemented on , But should pay attention to : be-all SIGTERM,SIGINT and SIGKILL Will lead to the interruption of the process .uv_kill The function is shown below :
uv_err_t uv_kill(int pid, int signum);
For using libuv The process started , You should use uv_process_kill End , It will be to uv_process_t As the first parameter , instead of pid. When using uv_process_kill after , Remember to use uv_close close uv_process_t.
Signals
libuv Yes unix Signals and some windows Under the mechanism ,uv_signal_t — Signal handle — libuv documentation, Well packed .
Use uv_signal_init initialization handle(uv_signal_t ), Then compare it with loop relation . In order to use handle Monitor specific signals , Use uv_signal_start() function . every last handle Can only be associated with one signal , Follow up uv_signal_start Will overwrite the previous Association . Use uv_signal_stop Stop monitoring . The following small example shows various uses :
signal/main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <uv.h>
uv_loop_t* create_loop()
{
uv_loop_t *loop = malloc(sizeof(uv_loop_t));
if (loop) {
uv_loop_init(loop);
}
return loop;
}
void signal_handler(uv_signal_t *handle, int signum)
{
printf("Signal received: %d\n", signum);
uv_signal_stop(handle);
}
// two signal handlers in one loop
void thread1_worker(void *userp)
{
uv_loop_t *loop1 = create_loop();
uv_signal_t sig1a, sig1b;
uv_signal_init(loop1, &sig1a);
uv_signal_start(&sig1a, signal_handler, SIGUSR1);
uv_signal_init(loop1, &sig1b);
uv_signal_start(&sig1b, signal_handler, SIGUSR1);
uv_run(loop1, UV_RUN_DEFAULT);
}
// two signal handlers, each in its own loop
void thread2_worker(void *userp)
{
uv_loop_t *loop2 = create_loop();
uv_loop_t *loop3 = create_loop();
uv_signal_t sig2;
uv_signal_init(loop2, &sig2);
uv_signal_start(&sig2, signal_handler, SIGUSR1);
uv_signal_t sig3;
uv_signal_init(loop3, &sig3);
uv_signal_start(&sig3, signal_handler, SIGUSR1);
while (uv_run(loop2, UV_RUN_NOWAIT) || uv_run(loop3, UV_RUN_NOWAIT)) {
}
}
int main()
{
printf("PID %d\n", getpid());
uv_thread_t thread1, thread2;
uv_thread_create(&thread1, thread1_worker, 0);
uv_thread_create(&thread2, thread2_worker, 0);
uv_thread_join(&thread1);
uv_thread_join(&thread2);
return 0;
}
Note
uv_run(loop, UV_RUN_NOWAIT) and uv_run(loop, UV_RUN_ONCE) Very much like , Because they only deal with one event . But the difference is ,UV_RUN_ONCE It will block when there is no task , however UV_RUN_NOWAIT Will return immediately . We use NOWAIT, This makes a loop Not because of another loop Starve for nothing to deal with .
When sending to the process SIGUSR1, You'll find that signal_handler The function is excited 4 Time , Each time corresponds to one uv_signal_t. then signal_handler call uv_signal_stop Terminated each uv_signal_t, The final program exits . For each handler In terms of functions , The assignment of tasks is very important . One uses more event-loop Server program for , Simply add signals to each process SIGINT The monitor , It can ensure that the program exits before interruption , Data can be saved safely . Child Process I/O
A normal newly generated process has its own set of file descriptor mapping tables , for example 0,1,2 They correspond to each other stdin,stdout and stderr. Sometimes the parent process wants to share its file descriptor mapping table with the child process . for example , Your program starts a subcommand , And output all error messages to log In file , But it can't be used stdout. therefore , You want to make your child process the same as your parent process , Have stderr. In this case ,libuv Provides the function of inheriting file descriptors . In the following example , We will call such a test program :
proc-streams/test.c
#include <stdio.h>
int main()
{
fprintf(stderr, "This is stderr\n");
printf("This is stdout\n");
return 0;
}
Actual execution procedures proc-streams During operation , Share only to child processes stderr. Use uv_process_options_t Of stdio File descriptor of domain setting subprocess . The first set stdio_count, Define the number of file descriptors .uv_process_options_t.stdio It's a uv_stdio_container_t Array . The definition is as follows :
typedef struct uv_stdio_container_s {
uv_stdio_flags flags;
union {
uv_stream_t* stream;
int fd;
} data;
} uv_stdio_container_t;
The above flag Values can be many . such as , If you are not going to use , It can be set to UV_IGNORE. If the stdio The corresponding first three file descriptors in are marked UV_IGNORE, Then they will be redirected to /dev/null.
Because we want to pass an existing file descriptor , So use UV_INHERIT_FD. therefore ,fd Is set to stderr.
proc-streams/main.c
int main() {
loop = uv_default_loop();
/* ... */
options.stdio_count = 3;
uv_stdio_container_t child_stdio[3];
child_stdio[0].flags = UV_IGNORE;
child_stdio[1].flags = UV_IGNORE;
child_stdio[2].flags = UV_INHERIT_FD;
child_stdio[2].data.fd = 2;
options.stdio = child_stdio;
options.exit_cb = on_exit;
options.file = args[0];
options.args = args;
int r;
if ((r = uv_spawn(loop, &child_req, &options))) {
fprintf(stderr, "%s\n", uv_strerror(r));
return 1;
}
return uv_run(loop, UV_RUN_DEFAULT);
}
Then you start proc-streams, That is to say main Generates an execution test Can be inherited by child processes. , You will only see “This is stderr”. You can try setting stdout Also inherit the parent process .
The above method can also be used for stream redirection . such as , hold flag Set to UV_INHERIT_STREAM, Then set the data.stream, At this time, the subprocess will only put this stream Take it as standard I/O. This can be used to achieve , for example :
https://en.wikipedia.org/wiki/Common_Gateway_Interface
A simple CGI An example of a script is as follows :
cgi/tick.c
#include <stdio.h>
#include <unistd.h>
int main() {
int i;
for (i = 0; i < 10; i++) {
printf("tick\n");
fflush(stdout);
sleep(1);
}
printf("BOOM!\n");
return 0;
}
CGI The server uses this chapter and The Internet The knowledge of that chapter , So every one client Will be sent 10 individual tick, Then be disconnected .
void on_new_connection(uv_stream_t *server, int status) {
if (status == -1) {
// error!
return;
}
uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
uv_tcp_init(loop, client);
if (uv_accept(server, (uv_stream_t*) client) == 0) {
invoke_cgi_script(client);
}
else {
uv_close((uv_handle_t*) client, NULL);
}
In the above code , We accepted the connection , And put socket( flow ) Pass to invoke_cgi_script.
cgi/main.c
args[1] = NULL;
/* ... finding the executable path and setting up arguments ... */
options.stdio_count = 3;
uv_stdio_container_t child_stdio[3];
child_stdio[0].flags = UV_IGNORE;
child_stdio[1].flags = UV_INHERIT_STREAM;
child_stdio[1].data.stream = (uv_stream_t*) client;
child_stdio[2].flags = UV_IGNORE;
options.stdio = child_stdio;
options.exit_cb = cleanup_handles;
options.file = args[0];
options.args = args;
// Set this so we can close the socket after the child process exits.
child_req.data = (void*) client;
int r;
if ((r = uv_spawn(loop, &child_req, &options))) {
fprintf(stderr, "%s\n", uv_strerror(r));
cgi Of stdout
Bound to socket On , So no matter tick What does the script program print , Will be sent to client End . By using processes , We can handle read and write concurrency well , And it's easy to use . But remember to do this , It's a waste of resources .
Pipes
libuv Of uv_pipe_t Structure may make some unix Programmers get confused , Because it changes like magic | and pipe(7). But here it is. uv_pipe_t Not at all IPC In the mechanism Anonymous pipeline ( stay IPC in ,pipe yes Anonymous pipeline , Only parent-child processes are allowed to communicate .FIFO Allow interprocess communication without kinship , obviously llibuv Inside uv_pipe_t Not the first ).uv_pipe_t Behind them unix Local socket perhaps windows Support for named pipelines , It can realize the communication between multiple processes . The following will be discussed in detail .
Parent-child IPC
Parent processes and child processes can communicate through simplex or duplex pipelines , The pipeline can be obtained by setting uv_stdio_container_t.flags by UV_CREATE_PIPE,UV_READABLE_PIPE perhaps UV_WRITABLE_PIPE The bitwise or value of . The above reading / Writing tags is for child processes .
Arbitrary process IPC
Since local socket Have a definite name , And it is marked by the location on the file system ( for example ,unix in socket Is a form of existence of documents ), Then it can be used to complete communication tasks between unrelated processes . Used by open source desktop environment D-BUS The system also uses local socket As an event notification , for example , When the news comes , Or when hardware is detected , Various applications will be notified .mysql The server is also running a local socket, Wait for client access .
When using local socket When , client / The server model is usually similar to before . After initialization , The method of sending and receiving messages is the same as before tcp similar , Next we also apply echo Server example .
pipe-echo-server/main.c
int main() {
loop = uv_default_loop();
uv_pipe_t server;
uv_pipe_init(loop, &server, 0);
signal(SIGINT, remove_sock);
int r;
if ((r = uv_pipe_bind(&server, "echo.sock"))) {
fprintf(stderr, "Bind error %s\n", uv_err_name(r));
return 1;
}
if ((r = uv_listen((uv_stream_t*) &server, 128, on_new_connection))) {
fprintf(stderr, "Listen error %s\n", uv_err_name(r));
return 2;
}
return uv_run(loop, UV_RUN_DEFAULT);
}
We put socket Name it echo.sock, It means that it will be created in the local folder . about stream API Come on , Local socekt Behave like tcp Of socket almost . You can use socat Test the server :
socat - /path/to/socket
If the client wants to connect with the server , You should use :
void uv_pipe_connect(uv_connect_t *req, uv_pipe_t *handle, const char *name, uv_connect_cb cb);
The above functions ,name Should be echo.sock.
ending file descriptors over pipes
The coolest thing is local socket File descriptors can be passed , That is, file descriptors can be exchanged between processes . This allows processes to transfer their I/O Pass to other processes . Its application scenarios include , Load balancing server , Assign work process, etc , Various can make cpu Use optimized applications .libuv Currently, only pipeline transmission is supported TCP sockets Or something pipes.
To demonstrate this function , In the future, we will implement a worker process in the loop client End request , Such a echo Server program . This program is a little complicated , In the tutorial, only part of the clip is intercepted , Just to understand , I recommend you to read the complete code :socat.
The worker process is simple , The file descriptor will be passed to it from the main process .
multi-echo-server/worker.c
uv_loop_t *loop;
uv_pipe_t queue;
int main() {
loop = uv_default_loop();
uv_pipe_init(loop, &queue, 1 /* ipc */);
uv_pipe_open(&queue, 0);
uv_read_start((uv_stream_t*)&queue, alloc_buffer, on_new_connection);
return uv_run(loop, UV_RUN_DEFAULT);
}
queue It is a pipe connected to the main process at the other end , therefore , File descriptors can be transferred . stay uv_pipe_init Lieutenant general ipc Parameter set to 1 Is the key , Because it indicates that this pipeline will be used for interprocess communication . Because the main process needs to put the file handle The worker process is assigned as standard input , So we use uv_pipe_open hold stdin As pipe( Don't forget ,0 representative stdin).
multi-echo-server/worker.c
void on_new_connection(uv_stream_t *q, ssize_t nread, const uv_buf_t *buf) {
if (nread < 0) {
if (nread != UV_EOF)
fprintf(stderr, "Read error %s\n", uv_err_name(nread));
uv_close((uv_handle_t*) q, NULL);
return;
}
uv_pipe_t *pipe = (uv_pipe_t*) q;
if (!uv_pipe_pending_count(pipe)) {
fprintf(stderr, "No pending count\n");
return;
}
uv_handle_type pending = uv_pipe_pending_type(pipe);
assert(pending == UV_TCP);
uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
uv_tcp_init(loop, client);
if (uv_accept(q, (uv_stream_t*) client) == 0) {
uv_os_fd_t fd;
uv_fileno((const uv_handle_t*) client, &fd);
fprintf(stderr, "Worker %d: Accepted fd %d\n", getpid(), fd);
uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read);
}
else {
uv_close((uv_handle_t*) client, NULL);
}
}
First , We call uv_pipe_pending_count To determine from handle Data can be read from . If your program can handle different types handle, At this time uv_pipe_pending_type Can be used to determine the current type . Although used here accept It looks strange , But it actually makes sense .accept The most common use is from other file descriptors ( Monitoring socket) Get file descriptor (client End ). In principle , It is the same as what we are going to do now : from queue Get file descriptor in (client). Next ,worker Can implement the standard echo The server is working .
Let's take a look at the main process , Observe how to start worker To achieve load balancing .
multi-echo-server/main.c
struct child_worker { uv_process_t req; uv_process_options_t options; uv_pipe_t pipe; } *workers;
child_worker
The structure wraps the process , And the pipeline connecting the main process and each independent process .
multi-echo-server/main.c
void setup_workers() {
round_robin_counter = 0;
// ...
// launch same number of workers as number of CPUs
uv_cpu_info_t *info;
int cpu_count;
uv_cpu_info(&info, &cpu_count);
uv_free_cpu_info(info, cpu_count);
child_worker_count = cpu_count;
workers = calloc(sizeof(struct child_worker), cpu_count);
while (cpu_count--) {
struct child_worker *worker = &workers[cpu_count];
uv_pipe_init(loop, &worker->pipe, 1);
uv_stdio_container_t child_stdio[3];
child_stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
child_stdio[0].data.stream = (uv_stream_t*) &worker->pipe;
child_stdio[1].flags = UV_IGNORE;
child_stdio[2].flags = UV_INHERIT_FD;
child_stdio[2].data.fd = 2;
worker->options.stdio = child_stdio;
worker->options.stdio_count = 3;
worker->options.exit_cb = close_process_handle;
worker->options.file = args[0];
worker->options.args = args;
uv_spawn(loop, &worker->req, &worker->options);
fprintf(stderr, "Started worker %d\n", worker->req.pid);
}
}
First , We use cool uv_cpu_info Function to get the current cpu Number of cores , So we can start the same number worker process . Again , Be sure to uv_pipe_init Of ipc Parameter set to 1. Next , We specify the stdin Is a readable pipeline ( From the perspective of subprocesses ). Everything that follows is very intuitive ,worker The process is started , Waiting for the file descriptor to be written into their standard input .
In the main process on_new_connection in , We received client Terminal socket, And pass it on to worker Next available in the ring worker process .
multi-echo-server/main.c
void on_new_connection(uv_stream_t *server, int status) {
if (status == -1) {
// error!
return;
}
uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
uv_tcp_init(loop, client);
if (uv_accept(server, (uv_stream_t*) client) == 0) {
uv_write_t *write_req = (uv_write_t*) malloc(sizeof(uv_write_t));
dummy_buf = uv_buf_init("a", 1);
struct child_worker *worker = &workers[round_robin_counter];
uv_write2(write_req, (uv_stream_t*) &worker->pipe, &dummy_buf, 1, (uv_stream_t*) client, NULL);
round_robin_counter = (round_robin_counter + 1) % child_worker_count;
}
else {
uv_close((uv_handle_t*) client, NULL);
}
} uv_write2
It can make a good abstraction in all situations , We just need to put client As a parameter, the transmission can be completed . Now? , Our multi process echo The server is already running .
边栏推荐
猜你喜欢
Mapreduce实例(九):Reduce端join
Servlet learning diary 8 - servlet life cycle and thread safety
运维,放过监控-也放过自己吧
A convolution substitution of attention mechanism
Kratos ares microservice framework (II)
Withdrawal of wechat applet (enterprise payment to change)
Reids之缓存预热、雪崩、穿透
[oc]- < getting started with UI> -- common controls uibutton
Servlet learning diary 7 -- servlet forwarding and redirection
Mapreduce实例(六):倒排索引
随机推荐
Redis' performance indicators and monitoring methods
小白带你重游Spark生态圈!
Workflow - activiti7 environment setup
Servlet learning diary 7 -- servlet forwarding and redirection
O & M, let go of monitoring - let go of yourself
Kratos ares microservice framework (II)
Redis' bitmap
Global and Chinese markets for small seed seeders 2022-2028: Research Report on technology, participants, trends, market size and share
In order to get an offer, "I believe that hard work will make great achievements
[oc]- < getting started with UI> -- common controls - prompt dialog box and wait for the prompt (circle)
Redis之主从复制
CAP理论
Master slave replication of redis
Mysql database recovery (using mysqlbinlog command)
AcWing 2456. Notepad
数据建模有哪些模型
解决小文件处过多
018.有效的回文
Kratos ares microservice framework (III)
Mathematical modeling 2004b question (transmission problem)