问题形容
不知你是否有过相似如下的需要:
有一些性能,它们足够繁多,但又须要后盾继续运行,以容器实现感觉太重了,以过程实现又太琐碎了,以线程实现能够承受然而又不好治理。
这类程序诸如:数据采集程序、可观测性程序、中间件、代理等等。
这一需要乍看之下倒是有点相似 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 的配置文件内容。
整个程序流程大抵如下:
- 初始化 Melon 库并运行多线程框架
-
调用
thread_create
函数对主线程做局部初始化操作,其中:- 构建子线程入口参数的字符指针数组
- 调用
mln_thread_create
创立子线程hello
- 设置定时器事件
timer_handler
,这个函数将每秒钟被调用一次
- 子线程
hello
被拉起,并 printf 输入函数名后,进入死循环调用mln_trace
函数(咱们前面马上说到这个函数) -
主线程每秒钟进入一次
timer_handler
并执行如下事项:- 如果
sw
为 0,则杀掉hello
线程,并再次设置定时器事件 - 如果
sw
为 1,则调用thread_create
创立hello
线程,并再次设置定时器事件 - 反转
sw
的值,放弃每秒敞开和启动hello
线程
- 如果
咱们能够看到,通过 mln_thread_create
和mln_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: 1
hello_entrance
[Hello,]
[Hello,]
[Hello,]
...
能够看到终端上会输入大量 [Hello,]
,这是脚本层输入的mln_trace
投递来的数据。两头会穿插着一些线程退出和启动的打印信息。
感激浏览!欢送各位对 Melon 感兴趣的读者拜访其 Github 仓库。