您的位置:首页 > 其它

基于mjpg-streamer-r63的源码分析之:基础知识详细解释[一]

2012-10-31 15:11 441 查看
**************************************************************************************************************************

作者:EasyWave 时间:2012.07.27

类别:linux应用之mjpg-streamer分析 声明:转载,请保留链接

注意:如有错误,欢迎指正。这些是我学习的日志文章......

***************************************************************************************************************************

在mjpg-streamer的开源的网络视频服务器项目中,在代码中会经常用到线程,在linux下的线程thread,下面来详细的分析和学习一下linux系统下的线程,如果是在ARM嵌入式系统中的应用程序要用到线程thread的话,需要在文件系统将libpthread-0.9.30.1.so,当然这个版本是需要看具体的arm-linux的编译器版本中所包含的版本号,只需拷贝到文件系统下的lib文件夹中,同时还需要将lib的路径配置好,这样应用程序能够找到这个libpthread-0.9.30.1.so库。这样就不会出现错误了。

一:线程的建立和退出

相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,而且拥有自己的栈空间,拥有独立的执行序列。在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程则正好相反。

通过pthread_create()函数来建立线程API 定义如下(POSIX线程相关的函数和变量定义都在头文件pthread.h):

int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void * arg)

参数:

thread:线程id返回值(无符号长整型数,bits/pthreadtypes.h);

attr: 默认值设置为NULL。(bits/pthreadtypes.h定义了结构体pthread_atttr_t);

属性值包括:

 可分离状态(detached state):PTHREAD_CREATE_JOINABLE(缺省值);PTHREAD_CREATE_DETACHED。

 调度策略(scheduling policy):SCHED_OTHER(正常、非实时,缺省值)、SCHED_RR(实时、轮转法)和SCHED_FIFO(实时、先入先出)。后两种调度策 略仅对超级用户有效,可通过函数pthread_setschedparam()来改变。

 调度参数(scheduling parameter):一个sched_param的机构,仅有一个整形变量表示线程运行的优先级。这个参数仅当调度策略为实时(SCHED_RR 和 SCHED_FIFO)时才有效,可通过函数pthread_setschedparam()来设置,缺省值为0。

 继承属性( inheritsched attribute ) :PTHREAD_EXPLICIT_SCHED ( 默认值) 和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策

略和调度参数(即attr中的值),而后者表示继承调用者线程的值。

 范围(scope):表示线程间竞争CPU 的范围,也就是说线程优先级的有效范围。POSIX 的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM 和 PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU 时间,后者表示仅与同进程中的线程竞争CPU 。目前LinuxThreads 仅实现了

PTHREAD_SCOPE_SYSTEM 值。

 守护池大小(guard size):表示线程守护池的大小,该属性控制守护池(guard area)的大小,直接影响到线程堆栈。该属性能有效抑制线程堆栈指针的溢出。

 堆栈地址(stack address):创建的线程堆栈的起始地址,最小值为PTHREAD_STACK_SIZE。

 堆栈大小( stack size ) : 堆栈大小, 该大小不小于PTHREAD_STACK_SIZE,当然也受限于系统限制。

void * (*start_routine)(void *):指向线程函数的函数指针(仅含有一个void *类型的参数)。

*arg:arg是void *类型的变量,但它同样可以作为任意类型的参数传给start_routine()函数。

二:线程资源释放的方法

Linux程序设计中,创建线程时调用pthread_create()函数。第二个参数attr为线程属性指针,一般情况下,我们创建线程时,若对线程属性没有特殊要求,都将此参数设为NULL,也就是使用了线程的默认属性--分离状态(joinable,或称可接合状态)。接下来主线程必须在适当的时候调用pthread_join(),同步(join,或等待,同步)子线程,同时释放线程本身占用的资源;否则,线程资源将驻留内存,直到整个进程退出为止。若该进程不断的创建线程,则每创建一次线程都会导致内存资源的消耗,很明显,这已经构成了内存泄漏!

对于线程资源的释放,有两种实现方法:

(1) 线程创建时,默认属性是可接合的(joinable),那就需要主线程来等待,所以在创建这个线程后适当的地方必须调用pthread_join()来等待子线程结束执行,否则就会引起内存泄漏!调用pthread_join()来等待子线程执行结束,这是 Linux同步主线程和子线程的一种机制,同时释放子线程的资源(线程描述符和堆栈(thread descriptor and stack))。假如使用默认线程属性,即线程属性为joinable,而又没有调用pthread_join(),那么该进程退出前,所创建的线程占用的资源便不会被释放(kind
of like a zombie process),因此造成内存泄漏。假如你不想或没有必要同步主线程和子线程,那么就把子线程属性设置为detached分离状态,那么子线程结束执行后会自行销毁其占用的资源

(2) 将线程属性设为分离状态(detached),这样子线程就属于“自灭”:子线程函数启动后跟主线程不再有“父子”关系(等待和被等待),退出线程时其资源会自动释放。注意:创建线程时,若属性参数为NULL,则线程属性默认为可接合的(joinable,即需要主线程等待的)。可以在线程创建时将其属性设为分离状态(detached),也可在线程创建后将其属性设为分离的(detached)

1):线程建立代码

[cpp] view
plaincopyprint?

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

void *print_msg_function( void *ptr );

int main(int argc, char *argv[])

{

pthread_t thread1 , thread2;

char *msg1 = "Thread 1" ;

char *msa2 = "Thread 2" ;

int iret1 , iret2;

/* 创建两个线程执行相同的函数 */

iret1 = pthread_create( &thread1, NULL, print_msg_function, (void*)msg1 );

iret2 = pthread_create( &thread2, NULL, print_msg_function, (void*)msg2 );

/* 等待线程完成 */

pthread_join( thread1, NULL);

pthread_join( thread2, NULL);

printf("Thread 1 returns: % d\n" ,iret1 );

printf("Thread 2 returns: % d\n" ,iret2 );

exit(0);

}

void *print_msg_function( void *ptr )

{

char *msg;

msg = (char * )ptr;

printf("% s \n" , msg);

}

编译运行:

编译:

C 编译器: cc -lpthread pthread 1 .c



C ++ 编译器: g++ -lpthread pthread 1 .c

运行:./a.out

结果:

Thread 1

Thread 2

Thread 1 returns: 0

Thread 2 returns: 0

2):线程设置为joinable

[cpp] view
plaincopyprint?

#include <pthread.h>

void* thread_function (void* thread_arg)

{

/* 线程要完成的工作 */

……

pthread_exit(“Exiting from the thread _function!”);

}

int main(int argc, char *argv[])

{

pthread_t thr;

void* thread_result;

pthread_create (& thr, NULL, & thread_function, NULL);

/* 主线程做的工作 */

……

/* 等待线程结束退出 */

pthread_join(thr, & thread_result);

return 0;

}

3):线程设置为detached

[cpp] view
plaincopyprint?

/* 线程创建后,通过调用pthread_detach()来设置 */

#include <pthread.h>

void* thread_function (void* thread_arg)

{

/* 线程要完成的工作 */

……

pthread_exit(“Exiting from the thread _function!”);

}

int main(int argc, char *argv[])

{

pthread_t thread;

pthread_create(&thread, NULL, &thread_function, NULL);

pthread_detach(thread); /* D o w ork here... */

/* 主线程做的工作 */

……

/* 不需要join等待线程退出 */

return 0;

}

三:线程同步

尽管在Posix 中同样可以使用IPC 的信号量机制来实现互斥锁mutex功能,但显然semphore的功能过于强大了,在Posix 中定义了另外一套专门用于线程同步的mutex函数

1):互斥锁创建和注销

POSIX定义了两种方法创建互斥锁,静态方式和动态方式:

pthread_mutex_t mutex = PTHREA D_M UTEX_IN ITIA LIZER ;

int pthread _mutex_init(pthread_mutex_t *mutex, const pthread_mutex attr_t *mutex_attr);

int pthread _mutex_destroy(pthread_mutex_t *mutex);


参数:

*mutex:pthread_mutex_t结构体;

*mutex_attr:指定互斥锁属性(属性的问题,大家可以到网络上去搜索啦,这里不多将了),如果为NULL,则使用缺省属性。Linux 实现中,

用宏PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁;动态方式是采用pthread_mutex_init()函数来初始化互斥锁。销毁一个互斥锁即意味着释放它所占用的资源,且要求互斥锁当前处于开放状态。但在Linux 中,互斥锁并不占用任何资源,因此销毁互斥锁pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有

其他动作。

2):锁操作

锁操作主要包括加锁(调用函数pthread_mutex_lock())、解锁(调用函数pthread_mutex_unlock() ) 和测试加锁( 调用函数pthread_mutex_trylock())三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。

int pthread _mutex_lock(pthread_mutex_t *m utex)

int pthread _mutex_unlock(pthread_mutex_t *m utex)

int pthread _mutex_trylock(pthread_mutex_t *m utex)


pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。

线程锁操作示例代码:

[cpp] view
plaincopyprint?

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

void *function_count(void);

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;

int counter = 0;

int main(int argc, char *argv[])

{

int rc1 , rc2;

pthread_t thread1 , thread2;

/* 创建两个独立的线程,分别都调用函数 function_count */

if( (rc1=pthread_create( &thread1, NULL,(void *)function_count, NULL)) )

{

printf("Thread creation failed : % d\n" , rc1 );

}

if( (rc2=pthread_create( &thread2, NULL,(void *)function_count, NULL)) )

{

printf("Thread creation failed : % d\n" , rc2);

}

/* 等待线程结束 */

pthread_join( thread1, NULL);

pthread_join( thread2, NULL);

exit(0);

}

void *function_count(void)

{

pthread_mutex_lock( &mutex1 );

counter++;

pthread_mutex_unlock( &mutex1 );

printf("C ounter value: % d\n" ,counter);

}

四:条件变量

条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待“条件变量的条件成立”而挂起;另一个线程使“条件成立”(给出条件成立信号)。为了防止竞争,条件变量总是和互斥锁结合起来使用。

1):POSIX定义了两种方法创建条件变量,静态方式和动态方式:

pthread_cond_t cond = PTH READ_COND_INITIA LIZER;

int pthread _cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

int pthread _cond_destroy(pthread_cond_t *cond);


参数:

*cond: pthread_cond_t的结构体;

*cond_attr:指定条件变量属性。尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。Linux实现中,用宏PTHREAD_COND_INITIALIZER静态初始化条件变量;动态方式是采用pthread_cond_init()函数来初始化条件变量。注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程等待该条件变量时才能注销该条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。

2):条件变量的等待和激发

条件变量的等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待。其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。

int pthread _cond_wait(pthread_cond_t *cond, pthread_mutex_t *m utex)

int pthread _cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *m utex, const struct tim espec *abstim e)

int pthread_cond_signal(pthread_cond_t *cond)

int pthread_cond_broadcast(pthread_cond_t *cond)


无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求条件等待pthread_cond_wait()(或pthread_cond_timedwait()。mutex互斥锁必须为普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),而且调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock())。在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁;条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入

pthread_cond_wait()后的解锁动作对应(实际上pthread_cond_wait()完成的操作包括:--解锁--挂起等待--加锁--,与pthread_cond_wait()前后的加锁、解锁正好对应)。

激发条件有两种形式:pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。

具体的实例这个就不说了,自己可以到网上去搜索。但主要的一点是:为了防止竞争,条件变量总是和互斥锁结合起来使用。这个在mjpg-streamer的代码中得到了体现!

下一篇博客将分析getopt_long_only和getopt_long函数的应用:基于mjpg-streamer-r63的源码分析之:基础知识详细解释[二]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: