libuv库学习笔记-processes

libuv学习(五) : Processes Introduction 对于基于事件(event-based)的程序来说, 有个限制是没办法很好地利用多核,提高CPU使用率. 即使能够使用多线程编程来分发 handle, 但是每个 loop 还是只有一个线程. 这时候, 使用多进程就能够分担 loop 的压力. 并且通过多进程 + 通信的方法, 会比 多线程 + 共享内存的方法更加安全, 易于开发. 创建进程 创建子进程 创... 阅读详情

Processes

libuv提供了相当多的子进程管理函数,并且是跨平台的,还允许使用stream,或者说pipe完成进程间通信。

在UNIX中有一个共识,就是进程只做一件事,并把它做好。因此,进程通常通过创建子进程来完成不同的任务(例如,在shell中使用pipe)。 一个多进程的,通过消息通信的模型,总比多线程的,共享内存的模型要容易理解得多。

当前一个比较常见的反对事件驱动编程的原因在于,其不能很好地利用现代多核计算机的优势。一个多线程的程序,内核可以将线程调度到不同的cpu核心中执行,以提高性能。但是一个event-loop的程序只有一个线程。实际上,工作区可以被分配到多进程上,每一个进程执行一个event-loop,然后每一个进程被分配到不同的cpu核心中执行。

Spawning child processes

一个最简单的用途是,你想要开始一个进程,然后知道它什么时候终止。需要使用uv_spawn完成任务:

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

由于上述的options是全局变量,因此被初始化为0。如果你在局部变量中定义options,请记得将所有没用的域设为0

uv_process_options_t options = {0};

uv_process_t只是作为句柄,所有的选择项都通过uv_process_options_t设置,为了简单地开始一个进程,你只需要设置file和args,file是要执行的程序,args是所需的参数(和c语言中main函数的传入参数类似)。因为uv_spawn在内部使用了execvp,所以不需要提供绝对地址。遵从惯例,实际传入参数的数目要比需要的参数多一个,因为最后一个参数会被设为NULL。

在函数uv_spawn被调用之后,uv_process_t.pid会包含子进程的id。

回调函数on_exit()会在被调用的时候,传入exit状态和导致exit的信号。

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);

在进程关闭后,需要回收handler。

Changing process parameters

在子进程开始执行前,你可以通过使用uv_process_options_t设置运行环境。

Change execution directory

设置uv_process_options_t.cwd,更改相应的目录。

Set environment variables

uv_process_options_t.env的格式是以null为结尾的字符串数组,其中每一个字符串的形式都是VAR=VALUE。这些值用来设置进程的环境变量。如果子进程想要继承父进程的环境变量,就将uv_process_options_t.env设为null。

Option flags

通过使用下面标识的按位或的值设置uv_process_options_t.flags的值,可以定义子进程的行为:

  • UV_PROCESS_SETUID-将子进程的执行用户id(UID)设置为uv_process_options_t.uid中的值。
  • UV_PROCESS_SETGID-将子进程的执行组id(GID)设置为uv_process_options_t.gid中的值。
    只有在unix系的操作系统中支持设置用户id和组id,在windows下设置会失败,uv_spawn会返回UV_ENOTSUP。
  • UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS-在windows上,uv_process_options_t.args参数不要用引号包裹。此标记对unix无效。
  • UV_PROCESS_DETACHED-在新会话(session)中启动子进程,这样子进程就可以在父进程退出后继续进行。请看下面的例子:

Detaching processes

使用标识UV_PROCESS_DETACHED可以启动守护进程(daemon),或者是使得子进程从父进程中独立出来,这样父进程的退出就不会影响到它。

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);

记住一点,就是handle会始终监视着子进程,所以你的程序不会退出。uv_unref()会解除handle。

Sending signals to processes

libuv打包了unix标准的kill(2)系统调用,并且在windows上实现了一个类似用法的调用,但要注意:所有的SIGTERM,SIGINT和SIGKILL都会导致进程的中断。uv_kill函数如下所示:

uv_err_t uv_kill(int pid, int signum);

对于用libuv启动的进程,应该使用uv_process_kill终止,它会以uv_process_t作为第一个参数,而不是pid。当使用uv_process_kill后,记得使用uv_close关闭uv_process_t。

Signals

libuv对unix信号和一些windows下类似的机制,做了很好的打包。

使用uv_signal_init初始化handle(uv_signal_t ),然后将它与loop关联。为了使用handle监听特定的信号,使用uv_signal_start()函数。每一个handle只能与一个信号关联,后续的uv_signal_start会覆盖前面的关联。使用uv_signal_stop终止监听。下面的这个小例子展示了各种用法:

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)和uv_run(loop, UV_RUN_ONCE)非常像,因为它们都只处理一个事件。但是不同在于,UV_RUN_ONCE会在没有任务的时候阻塞,但是UV_RUN_NOWAIT会立刻返回。我们使用NOWAIT,这样才使得一个loop不会因为另外一个loop没有要处理的事件而挨饿。

当向进程发送SIGUSR1,你会发现signal_handler函数被激发了4次,每次都对应一个uv_signal_t。然后signal_handler调用uv_signal_stop终止了每一个uv_signal_t,最终程序退出。对每个handler函数来说,任务的分配很重要。一个使用了多个event-loop的服务器程序,只要简单地给每一个进程添加信号SIGINT监视器,就可以保证程序在中断退出前,数据能够安全地保存。

Child Process I/O

一个正常的新产生的进程都有自己的一套文件描述符映射表,例如0,1,2分别对应stdin,stdout和stderr。有时候父进程想要将自己的文件描述符映射表分享给子进程。例如,你的程序启动了一个子命令,并且把所有的错误信息输出到log文件中,但是不能使用stdout。因此,你想要使得你的子进程和父进程一样,拥有stderr。在这种情形下,libuv提供了继承文件描述符的功能。在下面的例子中,我们会调用这么一个测试程序:

proc-streams/test.c
#include <stdio.h>

int main()
{
    fprintf(stderr, "This is stderr\n");
    printf("This is stdout\n");
    return 0;
}

实际的执行程序 proc-streams在运行的时候,只向子进程分享stderr。使用uv_process_options_t的stdio域设置子进程的文件描述符。首先设置stdio_count,定义文件描述符的个数。uv_process_options_t.stdio是一个uv_stdio_container_t数组。定义如下:

typedef struct uv_stdio_container_s {
  uv_stdio_flags flags;

  union {
    uv_stream_t* stream;
    int fd;
  } data;
} uv_stdio_container_t;

上边的flag值可取多种。比如,如果你不打算使用,可以设置为UV_IGNORE。如果与stdio中对应的前三个文件描述符被标记为UV_IGNORE,那么它们会被重定向到/dev/null。

因为我们想要传递一个已经存在的文件描述符,所以使用UV_INHERIT_FD。因此,fd被设为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);
}

这时你启动proc-streams,也就是在main中产生一个执行test的子进程,你只会看到“This is stderr”。你可以试着设置stdout也继承父进程。

同样可以把上述方法用于流的重定向。比如,把flag设为UV_INHERIT_STREAM,然后再设置父进程中的data.stream,这时子进程只会把这个stream当成是标准的I/O。这可以用来实现,例如CGI。

一个简单的CGI脚本的例子如下:

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服务器用到了这章和网络那章的知识,所以每一个client都会被发送10个tick,然后被中断连接。

cgi/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) {
        invoke_cgi_script(client);
    }
    else {
        uv_close((uv_handle_t*) client, NULL);
    }

上述代码中,我们接受了连接,并把socket(流)传递给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的stdout被绑定到socket上,所以无论tick脚本程序打印什么,都会发送到client端。通过使用进程,我们能够很好地处理读写并发操作,而且用起来也很方便。但是要记得这么做,是很浪费资源的。

Pipes

libuv的uv_pipe_t结构可能会让一些unix程序员产生困惑,因为它像魔术般变幻出|和pipe(7)。但这里的uv_pipe_t并不是IPC机制里的 匿名管道(在IPC里,pipe是 匿名管道,只允许父子进程之间通信。FIFO则允许没有亲戚关系的进程间通信,显然llibuv里的uv_pipe_t不是第一种)。uv_pipe_t背后有unix本地socket或者windows 具名管道的支持,可以实现多进程间的通信。下面会具体讨论。

Parent-child IPC

父进程与子进程可以通过单工或者双工管道通信,获得管道可以通过设置uv_stdio_container_t.flags为UV_CREATE_PIPE,UV_READABLE_PIPE或者UV_WRITABLE_PIPE的按位或的值。上述的读/写标记是对于子进程而言的。

Arbitrary process IPC

既然本地socket具有确定的名称,而且是以文件系统上的位置来标示的(例如,unix中socket是文件的一种存在形式),那么它就可以用来在不相关的进程间完成通信任务。被开源桌面环境使用的D-BUS系统也是使用了本地socket来作为事件通知的,例如,当消息来到,或者检测到硬件的时候,各种应用程序会被通知到。mysql服务器也运行着一个本地socket,等待客户端的访问。

当使用本地socket的时候,客户端/服务器模型通常和之前类似。在完成初始化后,发送和接受消息的方法和之前的tcp类似,接下来我们同样适用echo服务器的例子来说明。

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);
}

我们把socket命名为echo.sock,意味着它将会在本地文件夹中被创造。对于stream API来说,本地socekt表现得和tcp的socket差不多。你可以使用socat测试一下服务器:

$ socat - /path/to/socket

客户端如果想要和服务器端连接的话,应该使用:

void uv_pipe_connect(uv_connect_t *req, uv_pipe_t *handle, const char *name, uv_connect_cb cb);

上述函数,name应该为echo.sock。

Sending file descriptors over pipes

最酷的事情是本地socket可以传递文件描述符,也就是说进程间可以交换文件描述符。这样就允许进程将它们的I/O传递给其他进程。它的应用场景包括,负载均衡服务器,分派工作进程等,各种可以使得cpu使用最优化的应用。libuv当前只支持通过管道传输TCP sockets或者其他的pipes。

为了展示这个功能,我们将来实现一个由循环中的工人进程处理client端请求,的这么一个echo服务器程序。这个程序有一些复杂,在教程中只截取了部分的片段,为了更好地理解,我推荐你去读下完整的代码。

工人进程很简单,文件描述符将从主进程传递给它。

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是另一端连接上主进程的管道,因此,文件描述符可以传送过来。在uv_pipe_init中将ipc参数设置为1很关键,因为它标明了这个管道将被用来做进程间通信。因为主进程需要把文件handle赋给了工人进程作为标准输入,因此我们使用uv_pipe_open把stdin作为pipe(别忘了,0代表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);
    }
}

首先,我们调用uv_pipe_pending_count来确定从handle中可以读取出数据。如果你的程序能够处理不同类型的handle,这时uv_pipe_pending_type就可以用来决定当前的类型。虽然在这里使用accept看起来很怪,但实际上是讲得通的。accept最常见的用途是从其他的文件描述符(监听的socket)获取文件描述符(client端)。这从原理上说,和我们现在要做的是一样的:从queue中获取文件描述符(client)。接下来,worker可以执行标准的echo服务器的工作了。

我们再来看看主进程,观察如何启动worker来达到负载均衡。

multi-echo-server/main.c
struct child_worker {
    uv_process_t req;
    uv_process_options_t options;
    uv_pipe_t pipe;
} *workers;

child_worker结构包裹着进程,和连接主进程和各个独立进程的管道。

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);
    }
}

首先,我们使用酷炫的uv_cpu_info函数获取到当前的cpu的核心个数,所以我们也能启动一样数目的worker进程。再次强调一下,务必将uv_pipe_init的ipc参数设置为1。接下来,我们指定子进程的stdin是一个可读的管道(从子进程的角度来说)。接下来的一切就很直观了,worker进程被启动,等待着文件描述符被写入到他们的标准输入中。

在主进程的on_new_connection中,我们接收了client端的socket,然后把它传递给worker环中的下一个可用的worker进程。

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能够在所有的情形上做了一个很好的抽象,我们只需要将client作为一个参数即可完成传输。现在,我们的多进程echo服务器已经可以运转起来啦。

感谢Kyle指出了uv_write2需要一个不为空的buffer。

libuv介绍和实现的基本流程 文章目录前言一、libuv核心是什么?二、libuv简单介绍1.简单实现2.监视器3.handle的通用API总结 前言 libuv是一个高性能的,事件驱动的I/O库,并且提供了跨平台(如windows, linux)的API。 一、libuv核心是什么? libuv核心工作是提供一个event-loop,还有基于I/O和其它事件通知的回调函数。libuv还提供了一些核心工具,例如定时器,非阻塞的网络支持,异步文件系统访问,子进程等。 二、libuv简单介绍 1.简单实现 1、libuv的实现流程为: 阅读详情

相关推荐

C/C+++服务器之libuv的使用实战

C/C++服务器基础

小星AI 2454

异步I/O库-libuv介绍

libuv是一个跨平台的支持事件驱动的异步I/O的库,使开发者可以以非阻塞的方式执行文件I/O操作、网络通信、子进程管理等。事件循环:libuv有一个基于事件循环的模型,它不断地轮询事件,并在事件发生时调用相应的回调函数。异步I/O:libuv提供了异步文件I/O和网络I/O的接口,使得开发者可以执行I/O操作而不阻塞主线程。线程池:libuv使用线程池来处理一些不能以非阻塞方式执行的I/O操作,如文件系统操作在某些操作系统上。DNS解析:libuv提供了异步DNS解析的接口。

希望能帮助大家,希望大家多多支持,你们的支持是我前进的动力。 4153

libhv教程01--从零构建你的第一个高性能网络服务

本文详细介绍了如何使用libhv从零构建高性能网络服务,包括环境准备、HTTP服务实现、事件循环机制及性能调优。通过简洁的API和跨平台支持,libhv成为网络编程的高效工具,特别适合需要快速开发高性能网络服务的开发者。

weixin_30444105的博客 328

网络编程-libuv介绍

libuv整理

littleboy_IT 4486

Ubuntu 16.04下Netdata实时监控部署与调优指南

实时性能监控是保障老旧服务器稳定运行的关键技术,其核心在于低开销采集、毫秒级响应和零配置可视化。Netdata凭借嵌入式架构、C语言原生采集器及内存环形数据库,在资源受限环境中实现亚秒级指标刷新与Web实时渲染,显著优于Zabbix轮询延迟和Prometheus组件链路复杂度。该方案特别适配Ubuntu 16.04等ESM维护系统,兼容Linux 4.4内核与glibc 2.23,无需systemd新特性,通过procfs/sysfs接口直连内核,兼顾安全性与可观测性。本文聚焦netdata汉化实践与Ubu

weixin_34218579的博客 351

C++与Python并发并行编程实战:从概念到工程应用

并发与并行是现代编程中提升程序性能的核心概念。并发关注程序结构,通过任务切换在单核或多核上处理多个任务,提高资源利用率和响应速度;并行则依赖多核硬件,实现任务真正的同时执行以缩短计算时间。理解这两者的差异对于设计高效系统至关重要,尤其在处理I/O密集型或CPU密集型任务时。在工程实践中,C++通过std::thread和std::async提供底层控制,适合高性能系统;Python则因GIL限制,采用asyncio处理高并发I/O,利用multiprocessing实现CPU并行。掌握这些技术能优化Web服

weixin_33853794的博客 337

libuv 的Processes

libuv提供了相当多的子进程管理函数,并且是跨平台的,还允许使用stream,或者说pipe完成进程间通信。 在UNIX中有一个共识,就是进程只做一件事,并把它做好。因此,进程通常通过创建子进程来完成不同的任务(例如,在shell中使用pipe)。 一个多进程的,通过消息通信的模型,总比多线程的,共享内存的模型要容易理解得多。 当前一个比较常见的反对事件驱动编程的原因在于,其不能很好地利用现代多核计算机的优势。一个多线程的程序,内核可以将线程调度到不同的...

abc1231987的专栏 471

FastAPI+Triton生产级AI模型部署实战指南

机器学习模型部署不是简单运行predict(),而是涵盖模型固化、服务化架构、容器化交付与可观测性保障的完整工程体系。其核心原理在于解耦API网关与推理引擎,通过FastAPI的异步I/O能力处理高并发请求,借助Triton Inference Server实现GPU资源隔离、动态批处理与多模型版本管理,从而在真实生产环境中达成低延迟、高吞吐、可伸缩的服务目标。该方案广泛应用于金融风控、智能客服、工业质检等对SLA敏感的AI落地场景,尤其适合PyTorch模型向Kubernetes集群的规模化交付。

weixin_34345753的博客 408

Node.js多核利用终极指南:Backend-NodeJS-Golang-Interview_QA详解child_process与cluster实战

提到 Node.js 多核利用,很多新手都会困惑:Node.js 不是单线程吗?怎么还能用上多核 CPU?这个问题正是后端面试中的高频考点。开源项目 Backend-NodeJS-Golang-Interview_QA 是一份收录了 44 道 Node.js 面试题的免费题库,其中 child_process 与 cluster 相关的内容占了相当大的比重。本文将以该题库为线索,用通俗易懂的语言完

gitblog_00177的博客 965

spawn:一个简单的“ uv_spawn”包装器API即可执行和管理子进程,并行异步PHP用于阻止IO

产生 一个简单的uv_spawn包装器API,用于执行和管理子进程(用于阻止I / O的并行/异步PHP)。 该软件包使用Node.js库的 (PHP扩展 )的功能。 它的uv_spawn函数用于启动进程。 性能是一个更好的选择到了pcntl扩展,或使用proc_open 。 如果未安装libuv ,则此软件包将回退以使用[symfony / process]。 此程序包是我们的程序包的一部分,用于处理任何阻塞的I / O进程,而这些阻塞I / O进程不能被本地处理。 要了解更多关于libuv功能阅读在线教程。 安装 composer require symplely/spawn 如果可用,此软件包将使用libuv功能。 执行以下操作之一来安装。 对于像Debian这样的发行版,Ubuntu ... apt-get install libuv1-dev php-pear -y

01. VBA从入门到精通——录制宏、窗口、快捷键

excel表功能区右击→常用→选择显示开发工具选项→确定Visual Basic:打开VBA窗口宏:查看要录制的宏录制宏:准备录制宏使用相对引用:录制宏的时候对单元格的引用做一个设置宏安全性:对宏的使用做安全设置,一般设置为禁用所有宏,并发出通知,此时打开带有宏代码的工作薄时会出现警告标记,如果宏可信就选择启动。

weixin_43812198的博客 1696

Node.js child_process 深度解析:exec/spawn/fork 原理与生产避坑

child_process 是 Node.js 进程通信与系统集成的核心模块,其本质是 libuv 对 fork(2)、execve(2)、pipe(2) 等 POSIX 系统调用的封装。理解 exec(shell 封装+内存缓冲)、spawn(流式直连+零 shell 依赖)和 fork(Node.js 专用+内置 IPC)三者的底层差异,是解决 ENOENT 路径/链接库/挂载策略问题、Docker 容器中僵尸进程、Kubernetes 权限限制及文件描述符耗尽等高频故障的技术前提。本文聚焦工程落地,覆

weixin_33924312的博客 426

ArcGIS遥感影像分类技术在植被覆盖度计算中的应用

本文详细介绍了如何利用ArcGIS遥感影像分类技术高效计算植被覆盖度。通过数据准备、监督与非监督分类方法详解,并结合制表分析与栅格计算器进行面积统计,最终实现从分类图到覆盖度百分比的全流程自动化与可视化,为生态评估与规划提供精准数据支持。

weixin_29091445的博客 421

C++后端项目实战:REST API与幂等性设计,Nginx部署全流程指南

在Web服务开发中,REST API是构建前后端分离架构的核心技术,它通过标准的HTTP方法(GET、POST、PUT、DELETE)定义资源操作接口,实现客户端与服务器的解耦。其原理基于无状态通信,每个请求都包含处理所需的所有信息。这种设计带来了良好的可扩展性和可缓存性,技术价值在于简化了系统集成并提升了开发效率。幂等性是分布式系统中的一个关键概念,它确保同一操作的多次执行与单次执行产生相同的效果,这对于防止网络超时重试导致的数据不一致至关重要,尤其在金融交易、订单创建等场景中。结合Nginx这一高性能的

weixin_34090562的博客 410

Cadence Virtuoso IC617:从零构建传输门与反相器的Schematic设计实战

本文详细介绍了使用Cadence Virtuoso IC617进行传输门与反相器Schematic设计的完整流程,包括环境准备、原理图绘制、Symbol创建、仿真验证及实用技巧。通过分步指导和实战经验分享,帮助初学者快速掌握集成电路设计基础,提升EDA工具应用能力。

weixin_29194693的博客 600

推荐系统[九]项目技术细节讲解z2:搜索Query理解[Term Weight、Query 改写、同义词扩写]和语义召回技术

在 pooling 方法上,我们也尝试过不同层 pooling,或者在多层上增加 attention 聚合 BERT 多层结果,效果和最后一层所有 token 做 average pooling 相当。损失函数方面,在排序阶段使用pairwise比 pointwise 损失函数的效果会更好。

丨汀、的博客 1109

Python 异步协程:asyncio、async/await、aiohttp

Windows GUI 主线程消息循环是典型。

freeking101的博客 4390

22h2版本的win10安装Docker Desktop时页面空白

摘要:Windows10 22H2系统重装Docker时出现安装界面卡死问题,通过管理员CMD执行静默安装命令解决。命令指定了WSL-2后端及多个自定义数据存储路径(安装目录、Hyper-V数据、Windows容器数据、WSL数据),安装需等待几分钟。最终验证安装成功。

猿小白的博客 662

Linux进程状态解析与实战管理指南

进程状态是操作系统调度的核心概念,反映了进程在生命周期中的不同阶段。Linux系统通过R(运行)、S(可中断睡眠)、D(不可中断睡眠)、T(停止)和Z(僵尸)等状态标识符实现精细的进程管理。理解这些状态对于系统性能优化和故障排查至关重要,特别是在处理高并发服务或I/O密集型应用时。通过ps、top等命令监控进程状态分布,可以快速定位CPU瓶颈、僵尸进程累积或硬件I/O阻塞等问题。在容器化环境和云原生架构中,合理的进程状态管理能有效提升系统稳定性,避免因D状态进程导致的节点不可用或僵尸进程引发的资源泄漏。

weixin_30920853的博客 460

044、镜头脏污与异物检测:产线 AOI 检测方案与 ISP 脏污补偿

文章摘要(148字) 本文针对镜头生产中的脏污检测问题,提出产线AOI检测与ISP补偿结合的解决方案。产线端采用明场、暗场、偏振三通道联合检测技术,将油污漏检率从8.3%降至0.2%。ISP端通过RAW域局部梯度分析和频域补偿算法,动态修复产线漏检的微小脏污。作者结合实战案例,详细分析了传统灰度阈值法的局限性,并给出多模态检测的代码实现要点,强调必须建立"产线拦截+终端补偿"的双重防护机制。

sundong_81的博客 229

100天精通Python(可视化篇)——第89天:Bokeh库绘图可视化基础入门(参数说明+案例实战)

专栏导读 一、Bokeh是什么? 二、安装与导入 三、Bokeh接口介绍 四、创建图表 五、添加自定义渲染器 切换主题 添加图例 图例位置 图例方向 图例背景和边界 图例文本的外观 行列布局 网格布局

努力让自己发光,对的人才能迎着光而来 1万+

Android 手机的高级终端 Termux 安装使用、busybox、安卓 手机 web

手机已经 root安装 tsu,这是一个 su 的 Termux 版本,是一个真正的 root 权限,用来在 Termux上替代su,操作不慎可能对手机有安全风险,因为官方封装了,所以安装也很简单:安装 tsu:pkg install tsu -y切换到 root:tsu退出 root:exittsu切换 root 用户时会弹出 root 授权提示,给予其 root 权限即可。在管理员身份下输入 exit 可回到普通用户身份。

freeking101的博客 10万+

工业级4~20mA转0~3V信号调理电路原理图设计,已量产(电流传感器)

在工业现场,4~20mA 电流信号是模拟量传输的 “老熟人”—— 抗干扰强、能跑长距离,但嵌入式 MCU 的 ADC 一般只认电压信号。最近做项目时,我设计了一款能覆盖 0~25mA(含超量程冗余)、线性输出 0~3V 的调理电路,经过现场调试挺稳定,分享下细节。

不脱发的程序猿 528

Ubuntu 18.04 Node.js生产部署:PM2+NGINX全栈配置指南

Node.js生产环境部署远不止启动进程,本质是Linux系统、HTTP协议与运行时协同的工程实践。其核心原理在于进程守护(如PM2替代nohup)、反向代理(Nginx承担SSL终结、静态分发、连接管理等多层职责)及系统级调优(ulimit、systemd资源限制、时区/本地化配置)。技术价值体现在高可用(自动重启、零停机更新)、高性能(连接复用、缓冲优化、CPU多核利用)与强安全(HTTPS卸载、速率限制、CSP头加固)。典型应用场景包括Express/Koa后端服务、Vue/React全栈应用上线,尤

weixin_30399821的博客 312

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

敲敲云私有化部署:命令安装与Docker安装深度选型指南

零代码平台并非真正‘免运维’,其私有化部署本质是基础设施层的技术决策。理解部署方式需回归基础概念:容器化封装运行时环境、进程守护保障服务可用、环境隔离避免依赖冲突、配置中心化管理敏感参数。敲敲云作为可全量私有化交付的APaaS平台,其命令安装暴露底层系统依赖(如glibc版本、Node.js ABI兼容性),而Docker安装则通过镜像固化状态但引入启动时序与可观测性挑战。技术价值在于平衡可控性与可维护性——命令安装适合强运维能力团队,Docker安装适配CI/CD与标准化交付。典型应用场景包括金融级稳定运

weixin_33713707的博客 362

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

上一篇: libuv库学习笔记-networking
下一篇: 集团MySQL的酒店管理系统
onnx
博客等级 码龄5年 1万+粉丝 416原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

onnx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值