pjlib系列之線程thread及同步對象

操作系統抽象的API可以屏蔽操作系統特有的操作,使業務代碼方便在不同平臺移植。這部分實現主要是os_開頭的幾個文件,其中最重要的是os_core_unix.c(這裏選擇Linux平臺)。我把這些分拆3類:1、線程Thread和本地線程存儲TLS Thread Local Storage;2、線程同步對象;3、時間和高精度定時

線程本地存儲

線程共享進程的數據,訪問需要同步,有時線程需要有自己的私有數據,所以需要線程特有的數據空間。一個進程可以創建一個pthread_key_t,各線程使用該key設置私有數據。具體看下面四個函數的實現,使用全局變量thread_tls_id來存儲key。

pj_thread_local_alloc
pj_thread_local_free
pj_thread_local_set
pj_thread_local_get

線程結構體

struct pj_thread_t
{
    char	    obj_name[PJ_MAX_OBJ_NAME];	//線程名字
    pthread_t	    thread;					//線程id
    pj_thread_proc *proc;					//線程函數
    void	   *arg;						//線程參數
    pj_uint32_t	    signature1;				//標記1,已註冊線程會標記
    pj_uint32_t	    signature2;				//標記2

    pj_mutex_t	   *suspended_mutex;		//使用鎖模擬線程掛起

#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK!=0
    pj_uint32_t	    stk_size;		//當前棧使用大小
    pj_uint32_t	    stk_max_usage;	//線程棧最大限制
    char	   *stk_start;			//線程棧起始地址
    const char	   *caller_file;	//調用的源碼文件__FILE__
    int		    caller_line;		//調用的行數__LINE__
#endif
};

線程創建pj_thread_create

1、申請pj_thread_t結構體

2、填充線程名字thread_name

3、根據編譯選項是否自定義線程棧大小和自己申請線程棧空間

4、根據flag是否有PJ_THREAD_SUSPENDED,是否創建一個鎖模擬線程掛起

5、pthread_create( &rec->thread, &thread_attr, &thread_main, rec);創建原生線程,並且執行函數是thread_main,而不是用戶設置的函數

線程入口函數thread_main

1、設置本地存儲對象,對象爲pj_thread_create創建的pj_thread_t結構體

2、執行用戶線程函數

也就說,所以pj創建的線程,都是先執行默認函數thread_main,然後在thread_main纔回調用戶線程。在用戶線程中,需要註冊線程。

註冊線程pj_thread_register ( const char *cstr_thread_name, pj_thread_desc desc, pj_thread_t **ptr_thread)

註冊線程傳入一個數組參數pj_thread_desc,有128個long大小,該棧空間就是用來存儲線程私有數據,大小足夠存儲pj_thread_t對象。註冊線程比較完整地填充了新的pj_thread_t結構體,然後還是調用pj_thread_local_set設置線程本地私有數據。

線程的創建和註冊流程介紹完畢,根據線程示例pjlib/src/pjlib-test/thread.c,這裏有個地方我覺得不是很合理,即線程設置了兩次私有數據,而且兩次的對象並不一樣。第一次是創建線程時動態申請了pj_thread_t,並在thread_main設置。第二次是根據註冊函數通過棧參數空間desc進行設置,desc是棧局部變量,和創建線程時動態申請的pj_thread_t並不一致。所以第一次設置的pj_thread_t會別覆蓋,而那些值也沒有傳遞,比如pj_thread_t的proc arg,可能這些在線程跑起來以後就沒用了吧。對這個地方有理解不對的地方,歡迎大家指正。

主線程

主線程比較特殊,和其它線程不一樣的地方,主線程不需要調用pthread_create。主線程調用pj_init初始化pj庫,裏面調用pj_thread_init初始化主線程,在裏面創建所有後續先線程需要用到的pthread_key_t,然後調用pj_thread_register,主線程傳遞的desc變量是全局變量static pj_thread_t main_thread(注意和線程入口函數thread_main不一樣,命名顛倒,其實是兩個互不相干的東西)。

Linux原生線程

如果是在Linux原生線程想要使用pjlib,則需要如下代碼進行註冊。

// PJSIP在線程中調用出現提示註冊線程pj_thread_register的解決方案
pj_status_t pjcall_thread_register(void)
{
	pj_thread_desc	desc;
	pj_thread_t*	thread = 0;
	
	if (!pj_thread_is_registered())
	{
		return pj_thread_register(NULL, desc, &thread);
	}

	return PJ_SUCCESS;
}

其它的線程函數比較簡單,就不再一一描述

pj_thread_is_registered
pj_thread_get_prio
pj_thread_set_prio
pj_thread_get_prio_min
pj_thread_get_prio_max
pj_thread_get_os_handle
pj_thread_get_name
pj_thread_resume
pj_thread_this
pj_thread_join
pj_thread_destroy
pj_thread_sleep
pj_thread_check_stack
pj_thread_get_stack_max_usage
pj_thread_get_stack_info

線程同步對象

幾個線程同步對象基本是對pthread的封裝,沒有太多可講的,不再描述

os_core_unix.c
atomic:通話加鎖來實現安全的加減
mutex:通話傳入類型,可以創建一般的鎖或者recursive遞歸鎖
rwmutex:可以使用系統平臺的讀寫鎖,另外os_rwmutex.c文件使用mutex和sem實現仿真的讀寫鎖
critical:也是使用mutex來模擬
sem:
event:經典的cond+mutex

另外,pjlib還封裝了一個比較高層抽象的鎖lock.h和lock.c,還實現了羣組鎖功能,暫不深入研究羣組鎖的實現。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章