程序员人生 网站导航

pjlib线程实现简析

栏目:框架设计时间:2015-08-28 08:53:13
本篇主要讲授pjlib关于线程的实现方式

转载请注明出处:http://blog.csdn.net/lhl_blog/article/details/44063229

系统环境:
1. Ubuntu14.04 TLS 内核3.13.0⑷5-generi
2. gcc 4.8.2

3. glibc 2.19


开始之前需要讲授两个概念:

1.线程栈

    参考nono的csdn博文http://blog.csdn.net/dog250/article/details/7704898,其对linux的进程和线程栈进行了较为详细的介绍.个人认为比较重要的就是
    linux glibc所实现的线程栈为不能动态增长的,这就很值得注意了,如果线程中定义使用比较大的数据结构就会致使线程栈溢出,而线程栈是在进程的堆中分
    配的内存,从而城门失火,殃及池鱼,这样1旦线程栈溢出就会间接破坏进程地址空间,从而致使很难调试的bug(可能致使内存段毛病).因此,有效的管理线程
    栈在多线程编程中就成了比较重要的问题.或许,有的人会说,我在进行多线程编程时,对线程栈也没有进行过量干预啊,也没有出现过甚么问题啊? 这类认识
    实际上是存在问题的, 正所谓欠下的债早晚是要还的,是时候未到.

    至于表面上没有出现问题,主要缘由以下:

    1.*NIX系统在创建线程时,如果没有指定线程栈的大小,系统会创建1个默许大小的栈(8M),我们1般不会使用如此'大'的自动变量,但是如果线程定义了大量
    的自动变量,例如,定义了char array[8*1024*1024]的数组,不出意外程序会出现段毛病,示例代码以下:


#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),线程私有数据是存储和查询与某个线程相干的数据的1种机制.把这类数据称为线程私有数据或线程特定数据的缘由为, 希望每
  个线程可以独立的访问数据副本,而不用担心与其他线程的同步访问问题. 典型的利用为每一个线程具有自己的errno值, 各个线程之间的errno值互不影响.这就像
  身份证定义空间好比系统的进程空间,每一个独立的人好比1个单独的线程,每一个线程都有1个身份证号,这个身份证号就是我们的TLS,而且我们之间的身份证号
  不会相互影响,固然这个比喻有些不恰当,理论上身份证号是1成不变的.

3.pjlib线程实现

3.1 安全的线程栈保护机制

struct pj_thread_t
{
    char            obj_name[PJ_MAX_OBJ_NAME];
    pthread_t       thread;//linux 线程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;//线程栈大小j
    pj_uint32_t     stk_max_usage;//已使用的线程栈大小
    char            *stk_start;//线程栈的起始地址
    const char      *caller_file;//用于记录调用线程栈检测函数确当前位置
    int             caller_line;
#endif
};

pjlib线程描写符明肯定义了线程栈相干的元素, 通过这些元素, pjlib线程可以基本实现安全的线程栈使用.下面介绍pjlib线程栈的使用机制的实现方式.

3.1.1 线程栈初始化    

     pjlib的pj_thread_create实现pjlib线程的创建,其中函数的stack_size为线程栈大小参数,下面的代码只保存pj_thread_create函数的参数和线程栈相干的内容:
/*
 * 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支持两种线程栈创建方式,1.使用系统默许线程栈大小直接创建. 2.动态申请堆内存,然后将堆内存地址作为线程栈的首地址.创建完线程栈以后,pjlib在
     thread_main中对线程栈的起始地址进行了初始化.

3.1.2 线程栈保护机制 

    pjlib线程通过在每一个线程函数中调用PJ_CHECK_STACK()宏实现线程栈的保护机制,线程函数履行履行之前首先通过PJ_CHECK_STACK()监测当前线程栈状态,如果
    线程栈空间的使用率到达规定的警界值就会立即触发assert断言提示线程STACK OVERFLOW!,并退出.反之,PJ_CHECK_STACK()会重新更新当前线程栈的使用率.下
    面为PJ_CHECK_STACK()(pj_thread_check_stack)的实现:
/*
 * 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();

    /*计算当前线程栈的使用率*/
    usage = (&stk_ptr > thread->stk_start) ? &stk_ptr - thread->stk_start :
        thread->stk_start - &stk_ptr;

    /*如果线程栈的使用率超过警界值,立即触发断言*/
    pj_assert("STACK OVERFLOW!! " && (usage <= thread->stk_size - 128));

    /*重新统计线程栈使用率*/
    if (usage > thread->stk_max_usage) {
        thread->stk_max_usage = usage;
        thread->caller_file = file;
        thread->caller_line = line;
    }
}
    
    PJ_CHECK_STACK()1般在线程函数定义完局部变量时调用,这样就能够对stack overflow提早预警,从而减少大量的调试时间.

3.2 pjlib TLS

    pjlib线程基于TLS机制实现本地线程描写符的存储,所有pjlib线程或外部线程(linux原生线程)都必须使用pj_thread_register将自己的描写符存储到TLS中,
    当调用pjlib库函数时,库函数1般都会检测本地线程是不是注册过,即是不是调用过pthread_setspecific()注册线程TLS,否则pjlib库函数不能被正常使用,只有
    这样才能光明正大的使用pjlib函数.    

    pjlib这样做的理由可能为:
    1.每一个pjlib线程都有1个线程描写符pj_thread_t,每一个描写符中保存的内容各不相同,我们可以通过线程TLS将不同线程的描写符实现统1管理,就像errno.
    2.方便线程调试,通过TLS可以方便的获得本地线程相干的所有信息,我们通过打印或在线调试都可以方便的跟踪当前调用函数的线程状态.    
    3. ... ... 

3.3 pjlib线程调度策略

    pjlib线程封装了linux系统提供的几种线程调度策略,SCHED_FIFO,SCHED_RR,SCHED_OTHER,没有特别的设置.
------分隔线----------------------------
------分隔线----------------------------

最新技术推荐