使用 acl 庫的 C 庫編寫多線程程序

1、概述
  在當今強調多核開發的年代,要求程序員可以寫出高併發的程序,而利用多個核通常有兩種方式:採用多線程方式或多進程方式。每處理一個新任務時若是臨時 產生一個線程或進程且處理完任務後線程或進程便當即退出,顯示這種方式是很是低效的,因而人們通常採用線程池的模型(這在JAVA 或 .NET 中很是廣泛)或多進程進程池模型(這通常在UNIX平臺應用較多)。此外,對於線程池或進程池模型又分爲兩種情形:常駐留內存或半駐留內存,常駐內存是指 預先產生一批線程或進程,等待新任務到達,這些線程或進程即便在空閒狀態也會常駐內存;半駐留內存是指當來新任務時若是線程池或進程池沒有可利用線程或進 程則啓動新的線程或進程來處理新任務,處理完後,線程或進程並不當即退出,而是空閒指定時間,若是在空閒閥值時間到達前有新任務到達則當即處理新任務,如 果到達空閒超時後依然沒有新任務到達,則這些空閒的線程或進程便退出,以讓出系統資源。因此,對比常駐內存方式和半駐留內存方式,不難看出半駐留方式更有 按需分配的意味。
  下面僅以ACL框架中的半駐留線程池模型爲例介紹瞭如何寫一個半駐留線程池的程序。
2、半駐留線程池函數接口說明
2.1)線程池的建立、銷燬及任務添加等接口html

/**
 * 建立一個線程池對象
 * @param attr {acl_pthread_pool_attr_t*} 線程池建立時的屬性,若是該參數爲空,
 *  則採用默認參數: ACL_PTHREAD_POOL_DEF_XXX
 * @return {acl_pthread_pool_t*}, 若是不爲空則表示成功,不然失敗
*/
ACL_API acl_pthread_pool_t *acl_pthread_pool_create(const acl_pthread_pool_attr_t *attr);

/**
 * 銷燬一個線程池對象, 成功銷燬後該對象不能再用.
 * @param thr_pool {acl_pthread_pool_t*} 線程池對象,不能爲空
 * @return {int} 0: 成功; != 0: 失敗
 */
ACL_API int acl_pthread_pool_destroy(acl_pthread_pool_t *thr_pool);

/**
 * 向線程池添加一個任務
 * @param thr_pool {acl_pthread_pool_t*} 線程池對象,不能爲空
 * @param run_fn {void (*)(*)} 當有可用工做線程時所調用的回調處理函數
 * @param run_arg {void*} 回調函數 run_fn 所須要的回調參數
 * @return {int} 0: 成功; != 0: 失敗
 */
ACL_API int acl_pthread_pool_add(acl_pthread_pool_t *thr_pool,
        void (*run_fn)(void *), void *run_arg);

/**
 * 當前線程池中的線程數
 * @param thr_pool {acl_pthread_pool_t*} 線程池對象,不能爲空
 * @return {int} 返回線程池中的總線程數
 */
ACL_API int acl_pthread_pool_size(acl_pthread_pool_t *thr_pool);


2.2)線程池屬性設置接口git

/**
 * 初始化線程池屬性值
 * @param attr {acl_pthread_pool_attr_t*}
 */
ACL_API void acl_pthread_pool_attr_init(acl_pthread_pool_attr_t *attr);

/**
 * 設置線程池屬性中的最大堆棧大小(字節)
 * @param attr {acl_pthread_pool_attr_t*}
 * @param size {size_t}
 */
ACL_API void acl_pthread_pool_attr_set_stacksize(acl_pthread_pool_attr_t *attr, size_t size);

/**
 * 設置線程池屬性中的最大線程數限制值
 * @param attr {acl_pthread_pool_attr_t*}
 * @param threads_limit {int} 線程池中的最大線程數
 */
ACL_API void acl_pthread_pool_attr_set_threads_limit(acl_pthread_pool_attr_t *attr, 
                                                                                          int threads_limit);

/**
 * 設置線程池屬性中線程空閒超時值
 * @param attr {acl_pthread_pool_attr_t*}
 * @param idle_timeout {int} 線程空閒超時時間(秒)
 */
ACL_API void acl_pthread_pool_attr_set_idle_timeout(acl_pthread_pool_attr_t *attr,
                                                                                         int idle_timeout);

 

2.3)線程池中的工做線程建立、退出時設置回調函數接口程序員

/**
 * 添加註冊函數,在線程建立後當即執行此初始化函數
 * @param thr_pool {acl_pthread_pool_t*} 線程池對象,不能爲空
 * @param init_fn {int (*)(void*)} 工做線程初始化函數, 若是該函數返回 < 0,
 *  則該線程自動退出。
 * @param init_arg {void*} init_fn 所須要的參數
 * @return {int} 0: OK; != 0: Error.
 */
ACL_API int acl_pthread_pool_atinit(acl_pthread_pool_t *thr_pool,
        int (*init_fn)(void *), void *init_arg);

/**
 * 添加註冊函數,在線程退出當即執行此初函數
 * @param thr_pool {acl_pthread_pool_t*} 線程池對象,不能爲空
 * @param free_fn {void (*)(void*)} 工做線程退出前必須執行的函數
 * @param free_arg {void*} free_fn 所須要的參數
 * @return {int} 0: OK; != 0: Error.
 */
ACL_API int acl_pthread_pool_atfree(acl_pthread_pool_t *thr_pool,
        void (*free_fn)(void *), void *free_arg);

 

3、半駐留線程池例子
3.1)程序代碼github

#include "lib_acl.h"
#include <assert.h>

/**
 * 用戶自定義數據結構
 */
typedef struct THREAD_CTX {
	acl_pthread_pool_t *thr_pool;
	int   i;
} THREAD_CTX;

/* 全局性靜態變量 */
static acl_pthread_pool_t *__thr_pool = NULL;

/* 線程局部存儲變量(C99支持此種方式聲明,方便許多) */
static __thread unsigned int __local = 0;

static void work_thread_fn(void *arg)
{
	THREAD_CTX *ctx = (THREAD_CTX*) arg; /* 得到用戶自定義對象 */
	int   i = 5;

	/* 僅是驗證參數傳遞過程 */
	assert(ctx->thr_pool == __thr_pool);

	while (i-- > 0) {
		printf("thread start! tid=%d, i=%d, __local=%d\r\n",
				acl_pthread_self(), ctx->i, __local);
		/* 在本線程中將線程局部變量加1 */
		__local++;
		sleep(1);
	}

	acl_myfree(ctx);

	/* 至此,該工做線程進入空閒狀態,直到空閒超時退出 */
}

static int on_thread_init(void *arg)
{
	const char *myname = "on_thread_init";
	acl_pthread_pool_t *thr_pool = (acl_pthread_pool_t*) arg;

	/* 判斷一下,僅是爲了驗證參數傳遞過程 */
	assert(thr_pool == __thr_pool);
	printf("%s: thread(%d) init now\r\n", myname, acl_pthread_self());

	/* 返回0表示繼續執行該線程得到的新任務,返回-1表示中止執行該任務 */
	return (0);
}

static void on_thread_exit(void *arg)
{
	const char *myname = "on_thread_exit";
	acl_pthread_pool_t *thr_pool = (acl_pthread_pool_t*) arg;

	/* 判斷一下,僅是爲了驗證參數傳遞過程 */
	assert(thr_pool == __thr_pool);
	printf("%s: thread(%d) exit now\r\n", myname, acl_pthread_self());
}

static void run_thread_pool(acl_pthread_pool_t *thr_pool)
{
	THREAD_CTX *ctx;  /* 用戶自定義參數 */

	/* 設置全局靜態變量 */
	__thr_pool = thr_pool;

	/* 設置線程開始時的回調函數 */
	(void) acl_pthread_pool_atinit(thr_pool, on_thread_init, thr_pool);

	/* 設置線程退出時的回調函數 */
	(void) acl_pthread_pool_atfree(thr_pool, on_thread_exit, thr_pool);

	ctx = (THREAD_CTX*) acl_mycalloc(1, sizeof(THREAD_CTX));
	assert(ctx);
	ctx->thr_pool = thr_pool;
	ctx->i = 0;

	/**
	 * 向線程池中添加第一個任務,即啓動第一個工做線程
	 * @param thr_pool 線程池句柄
	 * @param workq_thread_fn 工做線程的回調函數
	 * @param ctx 用戶定義參數
	 */
	acl_pthread_pool_add(thr_pool, work_thread_fn, ctx);
	sleep(1);

	ctx = (THREAD_CTX*) acl_mycalloc(1, sizeof(THREAD_CTX));
	assert(ctx);
	ctx->thr_pool = thr_pool;
	ctx->i = 1;
	/* 向線程池中添加第二個任務,即啓動第二個工做線程 */
	acl_pthread_pool_add(thr_pool, work_thread_fn, ctx);
}

int main(int argc acl_unused, char *argv[] acl_unused)
{
	acl_pthread_pool_t *thr_pool;
	int  max_threads = 20;  /* 最多併發20個線程 */
	int  idle_timeout = 10; /* 每一個工做線程空閒10秒後自動退出 */
	acl_pthread_pool_attr_t attr;

	acl_pthread_pool_attr_init(&attr);
	acl_pthread_pool_attr_set_threads_limit(&attr, max_threads);
	acl_pthread_pool_attr_set_idle_timeout(&attr, idle_timeout);

	/* 建立半駐留線程句柄 */
	thr_pool = acl_pthread_pool_create(&attr);
	assert(thr_pool);
	run_thread_pool(thr_pool);

	if (0) {
		/* 若是當即運行 acl_pthread_pool_destroy,則因爲調用了線程池銷燬函數,
		 * 主線程便馬上通知空閒線程退出,全部空閒線程沒必要等待空閒超時時間即可退出,
		 */
		printf("> wait all threads to be idle and free thread pool\r\n");
		/* 當即銷燬線程池 */
		acl_pthread_pool_destroy(thr_pool);
	} else {
		/* 由於不當即調用 acl_pthread_pool_destroy,全部全部空閒線程都是當空閒
		 * 超時時間到達後才退出
		 */
		while (1) {
			int   ret;

			ret = acl_pthread_pool_size(thr_pool);
			if (ret == 0)
				break;
			printf("> current threads in thread pool is: %d\r\n", ret);
			sleep(1);
		}
		/* 線程池中的工做線程數爲0時銷燬線程池 */
		printf("> all worker thread exit now\r\n");
		acl_pthread_pool_destroy(thr_pool);
	}

	/* 主線程等待用戶在終端輸入任意字符後退出 */
	printf("> enter any key to exit\r\n");
	getchar();

	return (0);
}

 3.2) 編譯連接
  從 http://www.sourceforge.net/projects/acl/ 站點下載 acl_project 代碼,在WIN32平臺下請用VC2003編譯,打開 acl_project\win32_build\vc\acl_project_vc2003.sln 編譯後在目錄 acl_project\dist\lib_acl\lib\win32 下生成lib_acl_vc2003.lib, 而後在示例的控制檯工程中包含該庫,幷包含acl_project\lib_acl\include 下的 lib_acl.h 頭文件,編譯上述源代碼便可。
    由於本例子代碼在 ACL 的例子裏有,因此能夠直接編譯 acl_project\win32_build\vc\samples\samples_vc2003.sln 中的 thread_pool 項目便可。

3.3) 運行
  運行示例程序後,在個人機器的顯示結果以下:
on_thread_init: thread(23012) init now
thread start! tid=23012, i=0, __local=0
thread start! tid=23012, i=0, __local=1
> current threads in thread pool is: 2
on_thread_init: thread(23516) init now
thread start! tid=23516, i=1, __local=0
thread start! tid=23516, i=1, __local=1
> current threads in thread pool is: 2
thread start! tid=23012, i=0, __local=2
thread start! tid=23516, i=1, __local=2
thread start! tid=23012, i=0, __local=3
> current threads in thread pool is: 2
thread start! tid=23516, i=1, __local=3
thread start! tid=23012, i=0, __local=4
> current threads in thread pool is: 2
thread start! tid=23516, i=1, __local=4
> current threads in thread pool is: 2
> current threads in thread pool is: 2
> current threads in thread pool is: 2
> current threads in thread pool is: 2
> current threads in thread pool is: 2
> current threads in thread pool is: 2
> current threads in thread pool is: 2
> current threads in thread pool is: 2
> current threads in thread pool is: 2
> current threads in thread pool is: 2
> current threads in thread pool is: 2
on_thread_exit: thread(23012) exit now
> current threads in thread pool is: 1
on_thread_exit: thread(23516) exit now
> all worker thread exit now
> enter any key to exit

4、小結
    能夠看出,使用ACL庫建立半駐留式高併發多線程程序是比較簡單的,ACL線程池庫的接口定義及實現儘可能與POSIX中規定的POSIX線程的實現接口類似,建立與使用ACL線程池庫的步驟以下:
a) acl_pthread_pool_attr_init: 初始化建立線程池對象所須要屬性信息(能夠經過 acl_pthread_pool_attr_set_threads_limit 設置線程池最大併發數及用 acl_pthread_pool_attr_set_idle_timeout 設置線程池中工做線程的空閒退出時間間隔)
b) acl_pthread_pool_create: 建立線程池對象
c) acl_pthread_pool_add: 向線程池中添加新任務,新任務將由線程池中的某一工做線程執行
d) acl_pthread_pool_destroy: 通知並等待線程池中的工做線程執行完任務後退出,同時銷燬線程池對象
  還能夠在選擇在建立線程池對象後,調用 acl_pthread_pool_atinit 設置工做線程第一次被建立時回調用戶自定義函數過程,或當線程空閒退出後調用 acl_pthread_pool_atfree 中設置的回調函數。
    另外,能夠將建立線程池的過程進行一抽象,寫成以下形式:數據結構

/**
 * 建立半駐留線程池的過程
 * @return {acl_pthread_pool_t*} 新建立的線程池句柄
 */
static acl_pthread_pool_t *create_thread_pool(void)
{
	acl_pthread_pool_t *thr_pool;  /* 線程池句柄 */
	int  max_threads = 100;  /* 最多併發100個線程 */
	int  idle_timeout = 10;  /* 每一個工做線程空閒10秒後自動退出 */
	acl_pthread_pool_attr_t attr;  /* 線程池初始化時的屬性 */

	/* 初始化線程池對象屬性 */
	acl_pthread_pool_attr_init(&attr);
	acl_pthread_pool_attr_set_threads_limit(&attr, max_threads);
	acl_pthread_pool_attr_set_idle_timeout(&attr, idle_timeout);

	/* 建立半駐留線程句柄 */
	thr_pool = acl_pthread_pool_create(&attr);
	assert(thr_pool);
	return (thr_pool);
}

     其實,利用ACL建立線程池還有一個簡化接口(只因此叫 acl_thread_xxx 沒有加 p, 是由於這個接口不太遵照 Posix的一些規範),以下:多線程

/**
 * 更簡單地建立線程對象的方法
 * @param threads_limit {int}  線程池中最大併發線程數
 * @param idle_timeout {int} 工做線程空閒超時退出時間(秒),若是爲0則工做線程永不退出
 * @return {acl_pthread_pool_t*}, 若是不爲空則表示成功,不然失敗
 */
ACL_API acl_pthread_pool_t *acl_thread_pool_create(int threads_limit, int idle_timeout);

     這樣,用戶就能夠很是方便地建立本身的線程池了,並且別忘了,這個線程池仍是能夠是半駐留的(固然也是跨平臺的,能夠運行在 Linux/Solaris/FreeBSD/Win32 的環境下)。
    爲了方便使用ACL庫,用戶能夠參看ACL的在線幫助文檔:http://acl.sourceforge.net/acl_help/index.html。併發

我的微博:http://weibo.com/zsxxsz
框架

 下載:http://sourceforge.net/projects/acl/svn

svn:svn://svn.code.sf.net/p/acl/code/trunk
函數

github:https://github.com/zhengshuxin/acl

QQ 羣:242722074

相關文章
相關標籤/搜索