当前位置: 首页 > news >正文

开源C语言库Melon:多线程治理

问题描述

不知你是否有过类似如下的需求:

有一些功能,它们足够单一,但又需要后台持续运行,以容器实现感觉太重了,以进程实现又太琐碎了,以线程实现可以接受但是又不好管理。

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

这一需求乍看之下倒是有点类似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 "multithread";
log_path "/usr/local/melon/logs/melon.log";
trace_mode "trace/trace.m"; /* path or off */

这里主要关注三个配置:

  • framework必须是"multithread"
  • 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');
elsesys.print('worker process');Pipe('subscribe');
while (1) {ret = Pipe('recv');if (ret) {for (i = 0; i < sys.size(ret); ++i) {sys.print(ret[i]);}} fisys.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仓库。

相关文章:

  • 《数据库概述》 第七章 数据库设计
  • 6.OpenResty系列之深入理解(二)
  • PHPStudy快速搭建网站并结合内网穿透远程访问本地站点
  • 添加一个编辑的小功能(PHP的Laravel)
  • 计算机创新协会冬令营——暴力枚举题目03
  • 063:vue中一维数组与三维数组联动,类似购物车增减
  • 查看Linux系统内存、CPU、磁盘使用率和详细信息
  • Linux du和df命令
  • web学习笔记(十四)
  • spring-mvc数据绑定和表单标签库(介绍)
  • 51-5 Transformer 论文精读
  • Java反射获取实例并填充注解值
  • 2022 年全国职业院校技能大赛高职组云计算赛项试卷部分解析
  • 对象的复制
  • 【Android Studio】APP练手小项目——切换图片APP
  • 【译】JS基础算法脚本:字符串结尾
  • [译]Python中的类属性与实例属性的区别
  • 【EOS】Cleos基础
  • 【node学习】协程
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • CSS魔法堂:Absolute Positioning就这个样
  • iOS编译提示和导航提示
  • JavaScript函数式编程(一)
  • orm2 中文文档 3.1 模型属性
  • 给初学者:JavaScript 中数组操作注意点
  • 码农张的Bug人生 - 初来乍到
  • 如何打造100亿SDK累计覆盖量的大数据系统
  • 首页查询功能的一次实现过程
  • 推荐一个React的管理后台框架
  • 自定义函数
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • $NOIp2018$劝退记
  • (1)Nginx简介和安装教程
  • (4)事件处理——(7)简单事件(Simple events)
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (附源码)springboot 智能停车场系统 毕业设计065415
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (附源码)小程序儿童艺术培训机构教育管理小程序 毕业设计 201740
  • (转)IOS中获取各种文件的目录路径的方法
  • (转)母版页和相对路径
  • .net core 依赖注入的基本用发
  • .net 桌面开发 运行一阵子就自动关闭_聊城旋转门家用价格大约是多少,全自动旋转门,期待合作...
  • .net2005怎么读string形的xml,不是xml文件。
  • .NET使用存储过程实现对数据库的增删改查
  • .NET是什么
  • .NET应用架构设计:原则、模式与实践 目录预览
  • .pings勒索病毒的威胁:如何应对.pings勒索病毒的突袭?
  • /usr/local/nginx/logs/nginx.pid failed (2: No such file or directory)
  • @for /l %i in (1,1,10) do md %i 批处理自动建立目录
  • [ vulhub漏洞复现篇 ] JBOSS AS 5.x/6.x反序列化远程代码执行漏洞CVE-2017-12149
  • [2018/11/18] Java数据结构(2) 简单排序 冒泡排序 选择排序 插入排序
  • [Android Studio 权威教程]断点调试和高级调试