本文转载自自己头条号:https://www.toutiao.com/i6928...

转载请注明出处,感激!

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

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

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

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

咱们将逐个给出实例。

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

模块化线程

模块化线程是指,每一个线程都是一个独立的代码模块,都有各自对应的入口函数(相似于每一个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的局部。而与主线程通信的套接字则不用写在此处,而是线程启动后进入入口函数前主动增加的。

当初,就来启动程序吧。

$ ./launcherStart up worker process No.1Start 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:N02/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: 002/14/2021 04:07:49 GMT DEBUG: ./src/mln_thread_module.c:haha_main:42: PID:9309 !!!src:hello auto:9736 char:N02/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$ ./hellohello worldhello worldhello worldhello world...

此时,执行

$ top -d 1 -H -p PID

PID为hello程序的过程ID,则会看到,偶然会呈现两个线程(如果机器性能较好可能看不到,那么就缩短usleep的工夫即可)。


感激浏览,欢送大家留言评论。

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

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