C語言實用第三方庫Melon開箱即用之多線程模型

本文轉載自本人頭條號: 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(名字起得有點隨意)。下面,咱們以這兩個模塊爲例說明模塊化線程的開發和使用流程。函數

開發流程

這裏有幾點注意事項:性能

  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組成的。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函數,這個函數的做用是:在從當前線程模塊的入口函數返回至上層函數後,將會被調用,用於清理自定義資源。

每個線程模塊的清理函數只能被設置一個,屢次設置會被覆蓋,清理函數是線程獨立的,所以不會出現覆蓋其餘線程處理函數的狀況(固然,你也能夠故意這樣來構造,好比傳一個處理函數指針給別的模塊,而後那個模塊再進行設置)。

使用流程

使用流程遵循以下步驟:

  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, "%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...

相關文章
相關標籤/搜索