pjlib线程实现简析

本篇主要讲解pjlib关于线程的实现方式

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

系统环境:

1. Ubuntu14.04 TLS 内核3.13.0-45-generi

2. gcc 4.8.2

3. glibc 2.19

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

1.线程栈

参考nono的csdn博文http://blog.csdn.net/dog250/article/details/7704898,其对于linux的进程和线程栈进行了较为详细的介绍.个人认为比较重要的就是

linux glibc所实现的线程栈为不能动态增长的,这就很值得注意了,如果线程中定义使用比较大的数据结构就会导致线程栈溢出,而线程栈是在进程的堆中分

配的内存,从而城门失火,殃及池鱼,这样一旦线程栈溢出就会间接破坏进程地址空间,从而导致很难调试的bug(可能导致内存段错误).因而,有效的管理线程

栈在多线程编程中就成为了比较重要的问题.也许,有的人会说,我在进行多线程编程时,对于线程栈也没有进行过多干预啊,也没有出现过什么问题啊? 这种认识

其实是存在问题的, 正所谓欠下的债早晚是要还的,是时候未到.

至于表面上没有出现问题,主要原因如下:

1.*NIX系统在创建线程时,如果没有指定线程栈的大小,系统会创建一个默认大小的栈(8M),我们一般不会使用如此‘大‘的自动变量,但是如果线程定义了大量

的自动变量,例如,定义了char array[8*1024*1024]的数组,不出意外程序会出现段错误,示例代码如下:

#include <pthread.h>

#include <stdio.h>

#include <stdlib.h>

void* threadfunc(void* args)

{

//char c = ‘1‘;

//printf("c = %c\n", 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\n");

}

pthread_attr_init(&attr);

pthread_attr_getstacksize(&attr, &stack_size);

stack_size /= 1024 * 1024;

printf("default thread stacksize = %dMB\n", stack_size);

pthread_join(pid, NULL);

return 0;

}

2.线程TLS

线程TLS(thread locak store),线程私有数据是存储和查询与某个线程相关的数据的一种机制.把这种数据称为线程私有数据或线程特定数据的原因为, 希望每

个线程可以独立的访问数据副本,而不用担心与其他线程的同步访问问题. 典型的应用为每个线程拥有自己的errno值, 各个线程之间的errno值互不影响.这就像

身份证定义空间好比系统的进程空间,每个独立的人好比一个单独的线程,每个线程都有一个身份证号,这个身份证号就是我们的TLS,而且我们之间的身份证号

不会相互影响,当然这个比喻有些不恰当,理论上身份证号是一成不变的.

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

//第一种方式:

#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 */

//第二种方式:

#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()一般在线程函数定义完局部变量时调用,这样就可以对stack overflow提前预警,从而减少大量的调试时间.

3.2 pjlib TLS

pjlib线程基于TLS机制实现本地线程描述符的存储,所有pjlib线程或外部线程(linux原生线程)都必须使用pj_thread_register将自己的描述符存储到TLS中,

当调用pjlib库函数时,库函数一般都会检测本地线程是否注册过,即是否调用过pthread_setspecific()注册线程TLS,否则pjlib库函数不能被正常使用,只有

这样才能光明正大的使用pjlib函数.

pjlib这样做的理由可能为:

1.每个pjlib线程都有一个线程描述符pj_thread_t,每个描述符中保存的内容各不相同,我们可以通过线程TLS将不同线程的描述符实现统一管理,就像errno.

2.方便线程调试,通过TLS可以方便的获取本地线程相关的所有信息,我们通过打印或者在线调试都可以方便的跟踪当前调用函数的线程状态.

3. ... ...

3.3 pjlib线程调度策略

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

时间: 2024-08-24 15:34:26

pjlib线程实现简析的相关文章

JDK源码简析--java.lang包中的基础类库

题记 JDK,Java Development Kit. 我们必须先认识到,JDK只是,仅仅是一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含量来说,还是在一个层级上,它们都是需要被编译成字节码,在JRE中运行的,JDK编译后的结果就是jre/lib下得rt.jar,我们学习使用它的目的是加深对Java的理解,提高我们的Java编码水平. 本系列所有文章基于的JDK版本都是1.7.16. 本节内容 在本节中,简析java.lang包所包

.NET设计模式简析

首先,是设计模式的分类,我们知道,常用的设计模式共23种.但总体来说,设计模式氛围三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单列模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. 行为型模式,共十一种:策略模式.模版方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.转改模式.访问者模式.终结者模式.解释器模式. 另外还有并发型模式和线程池模式等. 介绍了分类,下面简单说下设计模式的六大原则

[转载] Thrift原理简析(JAVA)

转载自http://shift-alt-ctrl.iteye.com/blog/1987416 Apache Thrift是一个跨语言的服务框架,本质上为RPC,同时具有序列化.发序列化机制:当我们开发的service需要开放出去的时候,就会遇到跨语言调用的问题,JAVA语言开发了一个UserService用来提供获取用户信息的服务,如果服务消费端有PHP/Python/C++等,我们不可能为所有的语言都适配出相应的调用方式,有时候我们会很无奈的使用Http来作为访问协议;但是如果服务消费端不能

JDK框架简析--java.lang包中的基础类库、基础数据类型

题记 JDK.Java Development Kit. 我们必须先认识到,JDK不过,不过一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含量来说.还是在一个层级上,它们都是须要被编译成字节码.在JRE中执行的,JDK编译后的结果就是jre/lib下的rt.jar,我们学习使用它的目的是加深对Java的理解,提高我们的Java编码水平. 本系列全部文章基于的JDK版本号都是1.7.16. 源代码下载地址:https://jdk7.jav

Android -- MediaPlayer内部实现简析

Android -- MediaPlayer内部实现简析 在之前的博客中,已经介绍了使用MediaPlayer时要注意的内容.现在,这里就通过一个MediaPlayer代码实例,来进一步分析MediaPlayer内部是如何运作.实现的:当然这里的分析只截止到底层调用播放器之前,因为播放器这块实在是没搞懂. 我们使用的例子来源于之前MediaPlayer Playback译文中的官方实例: String url = "http://........"; // your URL here

简析 .NET Core 构成体系

简析 .NET Core 构成体系 Roslyn 编译器 RyuJIT 编译器 CoreCLR & CoreRT CoreFX(.NET Core Libraries) .NET Core 代码开发.部署.运行过程 总结 前文介绍了.NET Core 在整个.NET 平台所处的地位,以及与.NET Framework的关系(原文链接),本文将详细介绍.NET Core 框架的构成和各模块主要功能,以及如何实现跨平台. 上图描述了 .NET Core的系统构成,最上层是应用层,是开发基于UI应用的

Nutch学习笔记——抓取过程简析

Nutch学习笔记二--抓取过程简析 学习环境: ubuntu 概要: Nutch 是一个开源Java 实现的搜索引擎.它提供了我们运行自己的搜索引擎所需的全部工具.包括全文搜索和Web爬虫. 通过nutch,诞生了hadoop.tika.gora. 先安装SVN和Ant环境.(通过编译源码方式来使用nutch) apt-get install ant apt-get install subversion [email protected]:~/data/nutch$ svn co https:

服务器租用---常用网络协议:TCP和UDP的区别简析

服务器租用---常用网络协议:TCP和UDP的区别简析及TCP与UDP区别 TCP---传输控制协议,提供的是面向连接.可靠的字节流服务.当客户和服务器彼此交换数据前,必须先在双方之间建立 一个TCP连接,之后才能传输数据.TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一 端传到另一端. UDP---用户数据报协议,是一个简单的面向数据报的运输层协议.UDP不提供可靠性,它只是把应用程序传给IP层的 数据报发送出去,但是并不能保证它们能到达目的地.由于UDP在传输数据报

功能强大的图片截取修剪神器:Android SimpleCropView及其实例代码重用简析(转)

功能强大的图片截取修剪神器:Android SimpleCropView及其实例代码重用简析 SimpleCropView是github上第一个第三方开源的图片修剪截取利器,功能强大,设计良好.我个人认为SimpleCropView比附录文章1介绍的cropper更为强大和完备,但也更为复杂,如果是简单的应用场景,那么cropper也是一个不错的选择,SimpleCropView则适应图片裁剪截取复杂的需求任务.SimpleCropView在github上的项目主页是:https://githu