跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • C 语言实用第三方库 Melon 之多线程开发
未分類
12 2 月 2021

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

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

資深大佬 : monkeyNik 3

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

转载请注明出处,感谢!

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

在之前的文章中(开发利器——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. %sn", 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. %sn", n, strerror(errno));             return -1;         }         mln_log(debug, "!!!src:%S auto:%l char:%cn", 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. %sn", 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_process_handler(void *data) {     mln_log(none, "%sn", (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)

  • 主 資深大佬 : monkeyNik

    各位实在不好意思,之前 QQ 群设置有问题导致无法搜索到,感谢反馈,现在已经可以搜索到了。

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具