问题形容

不知你是否有过相似如下的需要:

有一些性能,它们足够繁多,但又须要后盾继续运行,以容器实现感觉太重了,以过程实现又太琐碎了,以线程实现能够承受然而又不好治理。

这类程序诸如:数据采集程序、可观测性程序、中间件、代理等等。

这一需要乍看之下倒是有点相似supervisor在做的事件,每个性能一个繁多后盾过程。诚然过程是一个抉择,然而理论应用中则会面临是大量的可执行程序和因人而异的开发格调。

当然,抉择多线程还有另一个重要起因,这里先卖个关子,咱们往下看。

解决方案

因而,笔者将介绍一个开源C语言库——Melon,它实现了一套多线程框架。在这套框架之下,每一个线程是一个独立的功能模块,并且能够承受来自主线程的治理。

对于 Melon 库,这是一个开源的 C 语言库,它具备:开箱即用、无第三方依赖、装置部署简略、中英文文档齐全等劣势。

Github repo

对于上述的问题,咱们能够应用这一框架来解决。除此之外,Melon还反对了另一个性能,这也是抉择多线程的起因之一,谜底将在示例中揭晓。

示例

在Melon的多线程框架中,有两种形式能够启动不同的线程模块,上面的示例将以动态创建和杀掉线程的形式进行演示。

#include <stdio.h>#include <errno.h>#include <unistd.h>#include "mln_core.h"#include "mln_log.h"#include "mln_thread.h"#include "mln_trace.h"int sw = 0; //开关switch缩写char name[] = "hello";static void thread_create(mln_event_t *ev);static int hello_entrance(int argc, char *argv[]){    printf("%s\n", __FUNCTION__);    while (1) {        mln_trace("s", "Hello");        usleep(10);    }    return 0;}static void timer_handler(mln_event_t *ev, void *data){    if (!sw) {        mln_string_t alias = mln_string("hello");        mln_thread_kill(&alias);        mln_event_timer_set(ev, 1000, NULL, timer_handler);    } else {        thread_create(ev);    }    sw = !sw;}static void thread_create(mln_event_t *ev){    char **argv = (char **)calloc(3, sizeof(char *));    if (argv != NULL) {        argv[0] = name;        argv[1] = NULL;        argv[2] = NULL;        mln_thread_create(ev, "hello", THREAD_DEFAULT, hello_entrance, 1, argv);        mln_event_timer_set(ev, 1000, NULL, timer_handler);    }}int main(int argc, char *argv[]){    struct mln_core_attr cattr;    cattr.argc = argc;    cattr.argv = argv;    cattr.global_init = NULL;    cattr.main_thread = thread_create;    cattr.worker_process = NULL;    cattr.master_process = NULL;    if (mln_core_init(&cattr) < 0) {       fprintf(stderr, "Melon init failed.\n");       return -1;    }    return 0;}

能够看到,main函数中只初始化了Melon库。而多线程框架也正是在库初始化时启动的。

咱们先对程序做大抵的形容,而后给出Melon的配置文件内容。

整个程序流程大抵如下:

  1. 初始化Melon库并运行多线程框架
  2. 调用thread_create函数对主线程做局部初始化操作,其中:

    1. 构建子线程入口参数的字符指针数组
    2. 调用mln_thread_create创立子线程hello
    3. 设置定时器事件timer_handler,这个函数将每秒钟被调用一次
  3. 子线程hello被拉起,并printf输入函数名后,进入死循环调用mln_trace函数(咱们前面马上说到这个函数)
  4. 主线程每秒钟进入一次timer_handler并执行如下事项:

    1. 如果sw为0,则杀掉hello线程,并再次设置定时器事件
    2. 如果sw为1,则调用thread_create创立hello线程,并再次设置定时器事件
    3. 反转sw的值,放弃每秒敞开和启动hello线程

咱们能够看到,通过mln_thread_createmln_thread_kill咱们能够让主线程动静的拉起和杀掉子线程。

为何应用多线程

因为咱们应用了mln_trace,这个宏函数是将C代码中数据投递到脚本层。这么做的益处是,这些数据不须要被写入日志文件,而后再启动另一个程序处理日志文件。也不须要手写C代码来将这些数据发送给远端。脚本层有内置的库函数能够轻松实现这些数据的解决、传输、入库等操作。

配置

说了很多对于程序性能的问题,但想要失常启动这个程序还须要正确配置Melon,配置文件内容如下:

log_level "none";//user "root";daemon off;core_file_size "unlimited";//max_nofile 1024;worker_proc 1;thread_mode on;framework on;log_path "/usr/local/melon/logs/melon.log";trace_mode "trace/trace.m"; /* path or off */

这里次要关注四个配置:

  • framework必须是on
  • thread_mode必须是on
  • trace_mode如果想启用mln_trace的性能,这里要给出脚本代码门路,否则给出off示意敞开该性能
  • worker_proc是工作过程数,咱们的多线程都是跑在工作过程上的,这样一旦线程有bug造成工作过程解体,主过程仍旧能够拉起新的工作过程持续运行

脚本代码

本例的脚本代码应用的就是Melon库中自带的默认脚本trace/trace.m

/* * Copyright (C) Niklaus F.Schen. */sys = Import('sys');if (MASTER)    sys.print('master process');else    sys.print('worker process');Pipe('subscribe');while (1) {    ret = Pipe('recv');    if (ret) {        for (i = 0; i < sys.size(ret); ++i) {            sys.print(ret[i]);        }    } fi    sys.msleep(1000);}Pipe('unsubscribe');

脚本次要工作就是死循环调用Pipe函数接管mln_trace投递来的数据,并向终端输入。

运行后果

...[Hello, ][Hello, ][Hello, ]01/29/2023 07:38:23 GMT REPORT: PID:15708 Child thread 'hello' exit.01/29/2023 07:38:23 GMT REPORT: PID:15708 child thread pthread_join's exit code: 1hello_entrance[Hello, ][Hello, ][Hello, ]...

能够看到终端上会输入大量[Hello, ],这是脚本层输入的mln_trace投递来的数据。两头会穿插着一些线程退出和启动的打印信息。

感激浏览!欢送各位对Melon感兴趣的读者拜访其Github仓库。