本文轉載自本人頭條號: 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...