pjlib線程實(shí)現(xiàn)簡(jiǎn)析
來(lái)源:程序員人生 發(fā)布時(shí)間:2015-08-28 08:53:13 閱讀次數(shù):5716次
本篇主要講授pjlib關(guān)于線程的實(shí)現(xiàn)方式
轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/lhl_blog/article/details/44063229
系統(tǒng)環(huán)境:
1. Ubuntu14.04 TLS 內(nèi)核3.13.0⑷5-generi
2. gcc 4.8.2
3. glibc 2.19
開(kāi)始之前需要講授兩個(gè)概念:
1.線程棧
參考nono的csdn博文http://blog.csdn.net/dog250/article/details/7704898,其對(duì)linux的進(jìn)程和線程棧進(jìn)行了較為詳細(xì)的介紹.個(gè)人認(rèn)為比較重要的就是
linux glibc所實(shí)現(xiàn)的線程棧為不能動(dòng)態(tài)增長(zhǎng)的,這就很值得注意了,如果線程中定義使用比較大的數(shù)據(jù)結(jié)構(gòu)就會(huì)致使線程棧溢出,而線程棧是在進(jìn)程的堆中分
配的內(nèi)存,從而城門失火,殃及池魚,這樣1旦線程棧溢出就會(huì)間接破壞進(jìn)程地址空間,從而致使很難調(diào)試的bug(可能致使內(nèi)存段毛病).因此,有效的管理線程
棧在多線程編程中就成了比較重要的問(wèn)題.或許,有的人會(huì)說(shuō),我在進(jìn)行多線程編程時(shí),對(duì)線程棧也沒(méi)有進(jìn)行過(guò)量干預(yù)啊,也沒(méi)有出現(xiàn)過(guò)甚么問(wèn)題啊? 這類認(rèn)識(shí)
實(shí)際上是存在問(wèn)題的, 正所謂欠下的債早晚是要還的,是時(shí)候未到.
至于表面上沒(méi)有出現(xiàn)問(wèn)題,主要緣由以下:
1.*NIX系統(tǒng)在創(chuàng)建線程時(shí),如果沒(méi)有指定線程棧的大小,系統(tǒng)會(huì)創(chuàng)建1個(gè)默許大小的棧(8M),我們1般不會(huì)使用如此'大'的自動(dòng)變量,但是如果線程定義了大量
的自動(dòng)變量,例如,定義了char array[8*1024*1024]的數(shù)組,不出意外程序會(huì)出現(xiàn)段毛病,示例代碼以下:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void* threadfunc(void* args)
{
//char c = '1';
//printf("c = %c
", c);
char *str1 = (char *)malloc(8*1024*1024);
char *str2 = (char *)malloc(8*1024*1024);
char array[8*1024*1024 - 1024*10] = {0};
}
int main(void)
{
pthread_t pid;
size_t stack_size;
pthread_attr_t attr;
int rc = pthread_create(&pid, NULL, threadfunc, NULL);
if(rc != 0) {
printf("create thread failed
");
}
pthread_attr_init(&attr);
pthread_attr_getstacksize(&attr, &stack_size);
stack_size /= 1024 * 1024;
printf("default thread stacksize = %dMB
", stack_size);
pthread_join(pid, NULL);
return 0;
}
2.線程TLS
線程TLS(thread locak store),線程私有數(shù)據(jù)是存儲(chǔ)和查詢與某個(gè)線程相干的數(shù)據(jù)的1種機(jī)制.把這類數(shù)據(jù)稱為線程私有數(shù)據(jù)或線程特定數(shù)據(jù)的緣由為, 希望每
個(gè)線程可以獨(dú)立的訪問(wèn)數(shù)據(jù)副本,而不用擔(dān)心與其他線程的同步訪問(wèn)問(wèn)題. 典型的利用為每一個(gè)線程具有自己的errno值, 各個(gè)線程之間的errno值互不影響.這就像
身份證定義空間好比系統(tǒng)的進(jìn)程空間,每一個(gè)獨(dú)立的人好比1個(gè)單獨(dú)的線程,每一個(gè)線程都有1個(gè)身份證號(hào),這個(gè)身份證號(hào)就是我們的TLS,而且我們之間的身份證號(hào)
不會(huì)相互影響,固然這個(gè)比喻有些不恰當(dāng),理論上身份證號(hào)是1成不變的.
3.pjlib線程實(shí)現(xiàn)
3.1 安全的線程棧保護(hù)機(jī)制
struct pj_thread_t
{
char obj_name[PJ_MAX_OBJ_NAME];
pthread_t thread;//linux 線程id
pj_thread_proc *proc;//線程處理函數(shù)
void *arg;//線程處理函數(shù)參數(shù)
pj_uint32_t signature1;//標(biāo)簽1,具體作用未知
pj_uint32_t signature2;//標(biāo)簽2
pj_mutex_t *suspended_mutex;//摹擬線程掛起動(dòng)作
//如果啟用了線程棧相干的檢查處理,則定義下面的成員變量:
#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK!=0
pj_uint32_t stk_size;//線程棧大小j
pj_uint32_t stk_max_usage;//已使用的線程棧大小
char *stk_start;//線程棧的起始地址
const char *caller_file;//用于記錄調(diào)用線程棧檢測(cè)函數(shù)確當(dāng)前位置
int caller_line;
#endif
};
pjlib線程描寫符明肯定義了線程棧相干的元素, 通過(guò)這些元素, pjlib線程可以基本實(shí)現(xiàn)安全的線程棧使用.下面介紹pjlib線程棧的使用機(jī)制的實(shí)現(xiàn)方式.
3.1.1 線程棧初始化
pjlib的pj_thread_create實(shí)現(xiàn)pjlib線程的創(chuàng)建,其中函數(shù)的stack_size為線程棧大小參數(shù),下面的代碼只保存pj_thread_create函數(shù)的參數(shù)和線程棧相干的內(nèi)容:
/*
* pj_thread_create(...)
*/
PJ_DEF(pj_status_t) pj_thread_create( pj_pool_t *pool,
const char *thread_name,
pj_thread_proc *proc,
void *arg,
pj_size_t stack_size,
unsigned flags,
pj_thread_t **ptr_thread)
{
...
#if PJ_HAS_THREADS
PJ_CHECK_STACK();
/* Set default stack size */
if (stack_size == 0)
stack_size = PJ_THREAD_DEFAULT_STACK_SIZE;
#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK!=0
rec->stk_size = stack_size;
rec->stk_max_usage = 0;
#endif
//第1種方式:
#if defined(PJ_THREAD_SET_STACK_SIZE) && PJ_THREAD_SET_STACK_SIZE!=0
/* Set thread's stack size */
rc = pthread_attr_setstacksize(&thread_attr, stack_size);
if (rc != 0)
return PJ_RETURN_OS_ERROR(rc);
#endif
/* PJ_THREAD_SET_STACK_SIZE */
//第2種方式:
#if defined(PJ_THREAD_ALLOCATE_STACK) && PJ_THREAD_ALLOCATE_STACK!=0
/* Allocate memory for the stack */
stack_addr = pj_pool_alloc(pool, stack_size);
PJ_ASSERT_RETURN(stack_addr, PJ_ENOMEM);
rc = pthread_attr_setstackaddr(&thread_attr, stack_addr);
if (rc != 0)
return PJ_RETURN_OS_ERROR(rc);
#endif
/* PJ_THREAD_ALLOCATE_STACK */
...
/* Create the thread. */
rec->proc = proc;
rec->arg = arg;
rc = pthread_create( &rec->thread, &thread_attr, &thread_main, rec);
if (rc != 0) {
return PJ_RETURN_OS_ERROR(rc);
}
...
}
static void *thread_main(void *param)
{
pj_thread_t *rec = (pj_thread_t*)param;
void *result;
pj_status_t rc;
#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK!=0
rec->stk_start = (char*)&rec;
#endif
....
}
pjlib支持兩種線程棧創(chuàng)建方式,1.使用系統(tǒng)默許線程棧大小直接創(chuàng)建. 2.動(dòng)態(tài)申請(qǐng)堆內(nèi)存,然后將堆內(nèi)存地址作為線程棧的首地址.創(chuàng)建完線程棧以后,pjlib在
thread_main中對(duì)線程棧的起始地址進(jìn)行了初始化.
3.1.2 線程棧保護(hù)機(jī)制
pjlib線程通過(guò)在每一個(gè)線程函數(shù)中調(diào)用PJ_CHECK_STACK()宏實(shí)現(xiàn)線程棧的保護(hù)機(jī)制,線程函數(shù)履行履行之前首先通過(guò)PJ_CHECK_STACK()監(jiān)測(cè)當(dāng)前線程棧狀態(tài),如果
線程棧空間的使用率到達(dá)規(guī)定的警界值就會(huì)立即觸發(fā)assert斷言提示線程STACK OVERFLOW!,并退出.反之,PJ_CHECK_STACK()會(huì)重新更新當(dāng)前線程棧的使用率.下
面為PJ_CHECK_STACK()(pj_thread_check_stack)的實(shí)現(xiàn):
/*
* pj_thread_check_stack()
* Implementation for PJ_CHECK_STACK()
*/
PJ_DEF(void) pj_thread_check_stack(const char *file, int line)
{
char stk_ptr;
pj_uint32_t usage;
pj_thread_t *thread = pj_thread_this();
/*計(jì)算當(dāng)前線程棧的使用率*/
usage = (&stk_ptr > thread->stk_start) ? &stk_ptr - thread->stk_start :
thread->stk_start - &stk_ptr;
/*如果線程棧的使用率超過(guò)警界值,立即觸發(fā)斷言*/
pj_assert("STACK OVERFLOW!! " && (usage <= thread->stk_size - 128));
/*重新統(tǒng)計(jì)線程棧使用率*/
if (usage > thread->stk_max_usage) {
thread->stk_max_usage = usage;
thread->caller_file = file;
thread->caller_line = line;
}
}
PJ_CHECK_STACK()1般在線程函數(shù)定義完局部變量時(shí)調(diào)用,這樣就能夠?qū)tack overflow提早預(yù)警,從而減少大量的調(diào)試時(shí)間.
3.2 pjlib TLS
pjlib線程基于TLS機(jī)制實(shí)現(xiàn)本地線程描寫符的存儲(chǔ),所有pjlib線程或外部線程(linux原生線程)都必須使用pj_thread_register將自己的描寫符存儲(chǔ)到TLS中,
當(dāng)調(diào)用pjlib庫(kù)函數(shù)時(shí),庫(kù)函數(shù)1般都會(huì)檢測(cè)本地線程是不是注冊(cè)過(guò),即是不是調(diào)用過(guò)pthread_setspecific()注冊(cè)線程TLS,否則pjlib庫(kù)函數(shù)不能被正常使用,只有
這樣才能光明正大的使用pjlib函數(shù).
pjlib這樣做的理由可能為:
1.每一個(gè)pjlib線程都有1個(gè)線程描寫符pj_thread_t,每一個(gè)描寫符中保存的內(nèi)容各不相同,我們可以通過(guò)線程TLS將不同線程的描寫符實(shí)現(xiàn)統(tǒng)1管理,就像errno.
2.方便線程調(diào)試,通過(guò)TLS可以方便的獲得本地線程相干的所有信息,我們通過(guò)打印或在線調(diào)試都可以方便的跟蹤當(dāng)前調(diào)用函數(shù)的線程狀態(tài).
3. ... ...
3.3 pjlib線程調(diào)度策略
pjlib線程封裝了linux系統(tǒng)提供的幾種線程調(diào)度策略,SCHED_FIFO,SCHED_RR,SCHED_OTHER,沒(méi)有特別的設(shè)置.
生活不易,碼農(nóng)辛苦
如果您覺(jué)得本網(wǎng)站對(duì)您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈(zèng)