本文转载自本人头条号: https://www.toutiao.com/i6928...转载请注明出处,感谢!git
在以前的文章中(开发利器——C 语言必备实用第三方库),笔者介绍了一款Linux/UNIX下C语言库Melon的基本功能,并给出了一个简单的多进程开箱即用的例子。github
本文将给你们介绍Melon中多线程的使用方法。shell
以前的文章中提到过,在Melon中有两种多线程模式:多线程
咱们将逐一给出实例。框架
Melon的Github仓库:https://github.com/Water-Melo...。socket
模块化线程是指,每个线程都是一个独立的代码模块,都有各自对应的入口函数(相似于每个C语言程序有一个main函数同样)。模块化
模块要存放于Melon/threads/目录下。在现有的Melon代码中,包含了两个示例模块——haha和hello(名字起得有点随意)。下面,咱们以这两个模块为例说明模块化线程的开发和使用流程。函数
这里有几点注意事项:性能
//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组成的。this
这个例子也很简单,就是利用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函数,这个函数的做用是:在从当前线程模块的入口函数返回至上层函数后,将会被调用,用于清理自定义资源。
每个线程模块的清理函数只能被设置一个,屡次设置会被覆盖,清理函数是线程独立的,所以不会出现覆盖其余线程处理函数的状况(固然,你也能够故意这样来构造,好比传一个处理函数指针给别的模块,而后那个模块再进行设置)。
使用流程遵循以下步骤:
咱们逐个步骤进行操做:
关于如何安装库,能够参考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_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, "%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-Melo...