**************************************************************************************************************************
作者: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):线程建立代码
- #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
- #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;
- }
- /* 线程创建后,通过调用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而不是挂起等待。
线程锁操作示例代码:
- #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的源码分析之:基础知识详细解释[二]