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,还实现了群组锁功能,暂不深入研究群组锁的实现。

 

你可能感兴趣的:(pjproject)