C 语言实用第三方库 Melon 之多线程开发 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
monkeyNik
V2EX    推广

C 语言实用第三方库 Melon 之多线程开发

  •  
  •   monkeyNik 2021 年 2 月 14 日 1830 次点击
    这是一个创建于 1791 天前的主题,其中的信息可能已经有所发展或是发生改变。

    本文转载自本人头条号: https://www.toutiao.com/i6928959491274703371/

    转载请注明出处,感谢!

    在之前的文章中(开发利器C 语言必备实用第三方库),笔者介绍了一款 Linux/UNIX 下 C 语言库 Melon 的基本功能,并给出了一个简单的多进程开箱即用的例子。

    本文将给大家介绍 Melon 中多线程的使用方法。

    之前的文章中提到过,在 Melon 中有两种多线程模式:

    • 模块化的多线程模式
    • 线程池

    我们将逐一给出实例。

    Melon 的 Github 仓库: https://github.com/Water-Melon/Melon

    模块化线程

    模块化线程是指,每一个线程都是一个独立的代码模块,都有各自对应的入口函数(类似于每一个 C 语言程序有一个 main 函数一样)。

    模块要存放于 Melon/threads/目录下。在现有的 Melon 代码中,包含了两个示例模块haha 和 hello (名字起得有点随意)。下面,我们以这两个模块为例说明模块化线程的开发和使用流程。

    开发流程

    这里有几点注意事项:

    1. 模块的名字:模块的名字将被用于两个地方,一个是配置文件中,一个是模块入口函数名。前者将在使用流程中说明,后者我们马上将以 haha 为例进行说明。
    2. 模块的参数:参数是在配置文件中给出的,这一点我们在使用流程中将会说明。但是需要注意一点,最后一个参数并不是配置文件中给出的,而是框架自动追加的,是主线程与该线程模块通信的 socketpair 套接字。
    //haha 模块 int haha_main(int argc, char **argv) { int fd = atoi(argv[argc-1]); mln_thread_msg_t msg; int nfds; fd_set rdset; for (;;) { FD_ZERO(&rdset); FD_SET(fd, &rdset); nfds = select(fd+1, &rdset, NULL, NULL, NULL); if (nfds < 0) { if (errno == EINTR) continue; mln_log(error, "select error. %s\n", strerror(errno)); return -1; } memset(&msg, 0, sizeof(msg)); int n = read(fd, &msg, sizeof(msg)); if (n != sizeof(msg)) { mln_log(debug, "write error. n=%d. %s\n", n, strerror(errno)); return -1; } mln_log(debug, "!!!src:%S auto:%l char:%c\n", msg.src, msg.sauto, msg.c); mln_thread_clearMsg(&msg); } return 0; } 

    可以看到,在这个例子中,模块的入口函数名为 haha_main 。对于每一个线程模块来说,他们的入口函数就是他们模块的名称(即文件名)+下划线+main组成的。

    这个例子也很简单,就是利用 select 持续关注主线程消息,当从主线程接收到消息后,就进行日志输出,然后释放资源。

    与之功能对应的就是 hello 这个模块:

    //hello 模块 #include <assert.h> static void hello_cleanup(void *data) { mln_log(debug, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); } int hello_main(int argc, char **argv) { mln_thread_setCleanup(hello_cleanup, NULL); int i; for (i = 0; i < 1; ++i) { int fd = atoi(argv[argc-1]); mln_thread_msg_t msg; memset(&msg, 0, sizeof(msg)); msg.dest = mln_string_new("haha"); assert(msg.dest); msg.sauto = 9736; msg.c = 'N'; msg.type = ITC_REQUEST; msg.need_clear = 1; int n = write(fd, &msg, sizeof(msg)); if (n != sizeof(msg)) { mln_log(debug, "write error. n=%d. %s\n", n, strerror(errno)); mln_string_free(msg.dest); return -1; } } usleep(100000); return 0; } 

    这个模块的功能也很简单,就是向主线程发送消息,而消息的接收方是 haha 模块,即主线程是一个中转站,它将 hello 模块的消息转发给 haha 模块。

    在 hello 这个模块中,调用了 mln_thread_setCleanup 函数,这个函数的作用是:在从当前线程模块的入口函数返回至上层函数后,将会被调用,用于清理自定义资源。

    每一个线程模块的清理函数只能被设置一个,多次设置会被覆盖,清理函数是线程独立的,因此不会出现覆盖其他线程处理函数的情况(当然,你也可以故意这样来构造,比如传一个处理函数指针给别的模块,然后那个模块再进行设置)。

    使用流程

    使用流程遵循如下步骤:

    1. 编写框架启动器
    2. 编译链接生成可执行程序
    3. 修改配置文件
    4. 启动程序

    我们逐个步骤进行操作:

    关于如何安装库,可以参考 Github 仓库说明或者之前的文章。

    我们先编写启动器:

    //launcher.c #include "mln_core.h" int main(int argc, char *argv[]) { struct mln_core_attr cattr; cattr.argc = argc; cattr.argv = argv; cattr.global_init = NULL; cattr.worker_process = NULL; return mln_core_init(&cattr); } 

    这里,我们不初始化任何全局变量,也不需要工作进程,因此都置空即可。

    $ cc -o launcher launcher.c -I /usr/local/melon/include/ -L /usr/local/melon/lib/ -lmelon -lpthread 

    生成名为 launcher 的可执行程序。

    此时,我们的线程尚不能执行,我们需要修改配置文件:

    log_level "none"; //user "root"; daemon off; core_file_size "unlimited"; //max_nofile 1024; worker_proc 1; thread_mode off; framework off; log_path "/usr/local/melon/logs/melon.log"; /* * Configurations in the 'exec_proc' are the * processes which are customized by user. * * Here is an example to show you how to * spawn a program. * keepalive "/tmp/a.out" ["arg1" "arg2" ...] * The command in this example is 'keepalive' that * indicate master process to supervise this * process. If process is killed, master process * would restart this program. * If you don't want master to restart it, you can * default "/tmp/a.out" ["arg1" "arg2" ...] * * But you should know that there is another * arugment after the last argument you write here. * That is the file descriptor which is used to * communicate with master process. */ exec_proc { // keepalive "/tmp/a"; } thread_exec { // restart "hello" "hello" "world"; // default "haha"; } 

    上面是默认配置文件,我们要进行如下修改:

    • thread_mode off; -> thread_mode on;
    • framework off; -> framework on;
    • thread_exec 配置块中的两项注释去掉

    这里,需要额外说明一下:

    thread_exec 配置块专门用于模块化线程之用,其内部每一个配置项均为线程模块。

    以 hello 为例:

    restart "hello" "hello" "world"; 

    restart 或者 default 是指令,restart 表示线程退出主函数后,再次启动线程。而 default 则表示一旦退出便不再启动。其后的 hello 字符串就是模块的名称,其余则为模块参数,即入口函数的 argc 和 argv 的部分。而与主线程通信的套接字则不必写在此处,而是线程启动后进入入口函数前自动添加的。

    现在,就来启动程序吧。

    $ ./launcher Start up worker process No.1 Start thread 'hello' Start thread 'haha' 02/14/2021 04:07:48 GMT DEBUG: ./src/mln_thread_module.c:haha_main:42: PID:9309 !!!src:hello auto:9736 char:N 02/14/2021 04:07:49 GMT DEBUG: ./src/mln_thread_module.c:hello_cleanup:53: PID:9309 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 02/14/2021 04:07:49 GMT REPORT: PID:9309 Thread 'hello' return 0. 02/14/2021 04:07:49 GMT REPORT: PID:9309 Child thread 'hello' exit. 02/14/2021 04:07:49 GMT REPORT: PID:9309 child thread pthread_join's exit code: 0 02/14/2021 04:07:49 GMT DEBUG: ./src/mln_thread_module.c:haha_main:42: PID:9309 !!!src:hello auto:9736 char:N 02/14/2021 04:07:49 GMT DEBUG: ./src/mln_thread_module.c:hello_cleanup:53: PID:9309 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 02/14/2021 04:07:49 GMT REPORT: PID:9309 Thread 'hello' return 0. 02/14/2021 04:07:49 GMT REPORT: PID:9309 Child thread 'hello' exit. 02/14/2021 04:07:49 GMT REPORT: PID:9309 child thread pthread_join's exit code: 0 ... 

    可以看到,事实上 Melon 中会启动工作进程来拉起其子线程,而工作进程数量由 worker_proc 配置项控制,如果多于一个,则每个工作进程都会拉起一组 haha 和 hello 线程。此外,我们也看到,hello 线程退出后,清理函数被调用。

    线程池

    线程池的使用则与框架基本无关,全部是对封装好的函数进行调用。

    这里我们将配置文件恢复为刚安装好时的默认配置。

    我们来看一个简单的例子:

    #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "mln_core.h" #include "mln_thread_pool.h" #include "mln_log.h" static int main_process_handler(void *data); static int child_process_handler(void *data); static void free_handler(void *data); int main(int argc, char *argv[]) { struct mln_core_attr cattr; struct mln_thread_pool_attr tpattr; cattr.argc = argc; cattr.argv = argv; cattr.global_init = NULL; cattr.worker_process = NULL; if (mln_core_init(&cattr) < 0) { return -1; } tpattr.dataForMain = NULL; tpattr.child_process_handler = child_process_handler; tpattr.main_process_handler = main_process_handler; tpattr.free_handler = free_handler; tpattr.cOndTimeout= 10; tpattr.max = 10; tpattr.cOncurrency= 10; return mln_thread_pool_run(&tpattr); } static int child_proces_handler(void *data) { mln_log(none, "%s\n", (char *)data); return 0; } static int main_process_handler(void *data) { int n; char *text; while (1) { if ((text = (char *)malloc(16)) == NULL) { return -1; } n = snprintf(text, 15, "hello world"); text[n] = 0; mln_thread_pool_addResource(text); usleep(1000); } } static void free_handler(void *data) { free(data); } 

    主函数中先对 Melon 框架做了初始化,主要是为了初始化日志,因为配置文件中将不启用框架。然后初始化线程池。

    程序功能比较简单,主线程创建资源然后分发资源,子线程拿到资源并日志输出。

    所有资源分发以及资源竞争全部封装在函数内部,回调函数只需要做功能逻辑处理即可。

    线程池被初始化为最大有 10 个子线程同时处理,若当前某一子线程闲置时间超过 10 秒,则会被回收。

    下面我们生成可执行程序并执行:

    $ cc -o hello hello.c -I /usr/local/melon/include/ -L /usr/local/melon/lib/ -lmelon -lpthread $ ./hello hello world hello world hello world hello world ... 

    此时,执行

    $ top -d 1 -H -p PID 

    PID 为 hello 程序的进程 ID,则会看到,偶尔会出现两个线程(如果机器性能较好可能看不到,那么就缩短 usleep 的时间即可)。


    感谢阅读,欢迎大家留言评论。

    再次给出 Melon 的官方 QQ 群:756582294

    Github 仓库: https://github.com/Water-Melon/Melon

    1 条回复    2021-02-16 21:46:52 +08:00
    monkeyNik
        1
    monkeyNik  
    OP
       2021 年 2 月 16 日
    各位实在不好意思,之前 QQ 群设置有问题导致无法搜索到,感谢反馈,现在已经可以搜索到了。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2641 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 11:27 PVG 19:27 LAX 03:27 JFK 06:27
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86