***************************************************************************************************************************
作者: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


2):线程设置为joinable

#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

/* 线程创建后,通过调用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的源码分析之:基础知识详细解释[二]

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐