当前位置: 首页 > Linux

C语言实用第三方库Melon多线程模型

时间:2023-04-06 19:33:06 Linux

开箱在上一篇文章(开发利器-C语言必备实用第三方库)中,笔者介绍了一个C语言库Melon下的基本功能Linux/UNIX,并给出了一个简单的多进程开箱即用的例子。本文将介绍如何在Melon中使用多线程。在上一篇文章中提到,Melon中有两种多线程模式:模块化多线程模式线程池我们将一一举例。Melon的Github存储库:https://github.com/Water-Melo…。模块化线程模块化线程是指每个线程都是一个独立的代码模块,有自己对应的入口函数(类似于每个C语言程序都有一个main函数)。模块应存储在Melon/threads/目录中。在现有的Melon代码中,有两个示例模块——haha和hello(名字有点随意)。下面,我们就以这两个模块为例,来说明模块化线程的开发和使用过程。开发过程中有几点需要注意:模块名称:模块的名称会用在两个地方,一个是在配置文件中,一个是模块入口函数的名称。前者会在使用过程中进行讲解,后者会以哈哈为例进行讲解。模块参数:参数在配置文件中给出,我们会在使用过程中进行说明。但需要注意的是,最后一个参数在配置文件中并没有给出,而是由框架自动添加的,是主线程用来与线程模块通信的socketpair套接字。//哈哈模块inthaha??_main(intargc,char**argv){intfd=atoi(argv[argc-1]);mln_thread_msg_t消息;国际金融机构;fd_set数据集;对于(;;){FD_ZERO(&rdset);FD_SET(fd,&rdset);nfds=select(fd+1,&rdset,NULL,NULL,NULL);if(nfds<0){if(errno==EINTR)继续;mln_log(error,"选择错误。%s\n",strerror(errno));返回-1;}memset(&msg,0,sizeof(msg));intn=read(fd,&msg,sizeof(msg));if(n!=sizeof(msg)){mln_log(debug,"写入错误。n=%d.%s\n",n,strerror(errno));返回-1;}mln_log(debug,"!!!src:%Sauto:%lchar:%c\n",msg.src,msg.sauto,msg.c);mln_thread_clearMsg(&msg);}return0;}可以看到在这个例子中,模块的入口函数名为haha_main。对于每一个线程模块,它们的入口函数由它们的模块名(即文件名)+下划线+main组成。这个例子也很简单,就是用select继续关注主线程的消息,当收到主线程的消息时,输出日志,释放资源。对应的函数是hello模块://hellomodule#includestaticvoidhello_cleanup(void*data){mln_log(debug,"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");}inthello_main(intargc,char**argv){mln_thread_setCleanup(hello_cleanup,NULL);诠释我;对于(i=0;i<1;++i){intfd=atoi(argv[argc-1]);mln_thread_msg_t消息;memset(&msg,0,sizeof(msg));消息。dest=mln_string_new("哈哈");断言(msg.dest);msg.sauto=9736;msg.c='N';msg.type=ITC_REQUEST;msg.need_clear=1;intn=write(fd,&msg,sizeof(msg));if(n!=sizeof(msg)){mln_log(debug,"写入错误。n=%d.%s\n",n,strerror(errno));mln_string_free(msg.dest);返回-1;}}usleep(100000);return0;}这个模块的功能也很简单,就是给主线程发送消息,消息的接收者就是哈哈模块,也就是主线程是一个中转站,它将hello模块的消息转发给haha模块。在hello模块中,调用了mln_thread_setCleanup函数。该函数的作用是:从当前线程模块的入口函数返回到上层函数后,会调用它来清理自定义资源。每个线程模块的清理函数只能设置一次,设置多次会被覆盖。cleanup函数是线程无关的,所以不会覆盖其他线程处理函数(当然你也可以故意这样构造,比如传递一个handler函数指针给另一个模块,然后那个模块自己设置).使用流程使用流程如下:编写框架启动器编译链接生成可执行程序修改配置文件启动程序我们一步步操作:关于如何安装库,可以参考Github仓库说明或之前文章。让我们先写启动器://launcher.c#include"mln_core.h"intmain(intargc,char*argv[]){structmln_core_attrcattr;cattr.argc=argc;cattr.argv=argv;cattr.global_init=NULL;cattr.worker_process=NULL;returnmln_core_init(&cattr);}在这里,我们没有初始化任何全局变量,也不需要工作进程,所以将它们留空即可。$cc-olauncherlauncher.c-I/usr/local/melon/include/-L/usr/local/melon/lib/-lmelon-lpthread生成名为launcher的可执行程序。此时,我们的线路尚不能执行,我们需要修改配置文件:log_level"none";//user"root";daemonoff;core_file_size"unlimited";//max_nofile1024;worker_proc1;thread_modeoff;frameworkoff;log_path"/usr/local/melon/logs/melon.log";/**'exec_proc'中的配置是*用户自定义的进程。**下面是一个示例,向您展示如何*生成一个程序。*keepalive"/tmp/a.out"["arg1""arg2"...]*这个例子中的命令是'keepalive'*指示主进程监督这个*进程。如果进程被终止,主进程*将重新启动该程序。*如果你不想让master重启它,你可以*default"/tmp/a.out"["arg1""arg2"...]**但你应该知道在最后一个参数之后还有另一个参数你在这里写的论点。*这是用于*与主进程通信的文件描述符。*/exec_proc{//keepalive"/tmp/a";}thread_exec{//restart"hello""hello""world";//default"haha";}上面是默认的配置文件,我们需要做如下修改:thread_modeoff;->线程模式开启;框架关闭;->框架上;这里去掉thread_exec配置块中的两条注释,需要补充说明:thread_exec配置块是专门用于modules线程的,每个内部配置项都是一个线程模块。以你好为例:重启“你好”“你好”“世界”;restart或default是一条指令,restart表示线程退出main函数后,线程再次启动。而default就是一旦退出就不会启动。下面的hello字符串是模块的名称,其余为模块参数,即入口函数的argc和argv部分。与主线程通信的socket不需要写在这里,而是在线程启动后进入入口函数前自动添加的。现在,让我们启动程序。$./launcherStartupworkerprocessNo.1Startthread'hello'Startthread'haha'02/14/202104:07:48GMTDEBUG:./src/mln_thread_module.c:haha_main:42:PID:9309!!!src:helloauto:9736char:N02/14/202104:07:49GMT调试:./src/mln_thread_module.c:hello_cleanup:53:PID:9309@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@02/14/202104:07:49GMT报告:PID:9309线程“你好”返回0.02/14/202104:07:49GMT报告:PID:9309子线程“你好”退出。02/14/202104:07:49GMT报告:PID:9309子线程pthread_join的退出代码:002/14/202104:07:49GMT调试:./src/mln_thread_module.c:haha_main:42:PID:9309!!!src:helloauto:9736char:N02/14/202104:07:49GMT调试:./src/mln_thread_module.c:hello_cleanup:53:PID:9309@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@02/14/202104:07:49GMT报告:PID:9309线程“你好”返回0.02/14/202104:07:49GMT报告:PID:9309子线程“你好”exit.02/14/202104:07:49GMT报告:PID:9309子线程pthread_join'sexitcode:0...可以看到,其实Melon会启动一个worker进程来拉起它的子线程,worker进程的数量由worker_proc配置项控制。如果不止一个,每个worker进程都会拉起一组haha和hello线程另外,我们还看到hello线程退出后,会调用cleanup函数。线程池线程池的使用与框架基本无关,都是对封装函数的调用。这里我们将配置文件恢复为刚安装时的默认配置。我们来看一个简单的例子:#include#include#include#include"mln_core.h"#include"mln_thread_pool.h"#include"mln_log.h"staticintmain_process_handler(void*data);staticintchild_process_handler(void*data);staticvoidfree_handler(void*data);intmain(intargc,char*argv[]){structmln_core_attrcattr;结构mln_thread_pool_attrtpattr;cattr.argc=argc;cattr.argv=argv;cattr.global_init=NULL;cattr.worker_process=NULL;如果(mln_core_init(&cattr)<0){返回-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;returnmln_thread_pool_run(&tpattr);}staticintchild_process_handler(void*data){mln_log(none,"%s\n",(char*)data);返回0;}统计icintmain_process_handler(void*data){intn;字符*文本;while(1){if((text=(char*)malloc(16))==NULL){return-1;}n=snprintf(文本,15,“你好世界”);文本[n]=0;mln_thread_pool_addResource(文本);睡眠(1000);}}staticvoidfree_handler(void*data){free(data);初始化就完成了,主要是初始化日志,因为在配置文件中不会启用框架,然后会初始化线程池。程序的功能比较简单。主线程创建资源然后分发,子线程获取资源并输出日志。所有的资源分配和资源竞争都封装在函数内部,回调函数只需要做功能逻辑处理即可。线程池初始化时最多同时处理10个子线程。如果一个子线程当前空闲时间超过10秒,就会被回收。下面我们生成一个可执行程序并执行它:$cc-ohellohello.c-I/usr/local/melon/include/-L/usr/local/melon/lib/-lmelon-lpthread$./hellohelloworldhelloworldhelloworldhelloworld...这时候执行$top-d1-H-pPIDPID是hello程序的进程号,你会看到偶尔会出现两个线程(如果机器性能好点,可能不会看到它,然后缩短它的睡眠时间)。感谢阅读,欢迎评论。再给Melon的官方QQ群:756582294Github仓库:https://github.com/Water-Melo...