精华内容
下载资源
问答
  • pthread

    千次阅读 2016-07-07 14:33:02
    POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作...pthread_create(pthread_t *restr
    POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。

    1、pthread相关函数

    #include <pthread.h>
    //新建线程
    int pthread_create(pthread_t *restrict tidp, constpthread_attr_t *restrict attr,void*(*start_rtn)(void*),void *restrict arg);
      
    //线程终止
    void pthread_exit(void *rval_ptr);//线程自身主动退出
    int pthread_join(pthread_t tid, void **rval_ptr);//其他线程阻塞自身,等待tid退出
      
    //线程清理
    voidpthread_cleanup_push(void(*rtn)(void*), void *arg);
    voidpthread_cleanup_pop(intexecute);
     
    另:
     
    操纵函数
    pthread_create():创建一个线程
    pthread_exit():终止当前线程
    pthread_cancel():中断另外一个线程的运行
    pthread_join():阻塞当前的线程,直到另外一个线程运行结束
    pthread_attr_init():初始化线程的属性
    pthread_attr_setdetachstate():设置脱离状态的属性(决定这个线程在终止时是否可以被结合)
    pthread_attr_getdetachstate():获取脱离状态的属性
    pthread_attr_destroy():删除线程的属性
    pthread_kill():向线程发送一个信号
     

    Pthread同步函数

    用于 mutex 和条件变量
    pthread_mutex_init() 初始化互斥锁
    pthread_mutex_destroy() 删除互斥锁
    pthread_mutex_lock():占有互斥锁(阻塞操作)
    pthread_mutex_trylock():试图占有互斥锁(不阻塞操作)。即,当互斥锁空闲时,将占有该锁;否则,立即返回。
    pthread_mutex_unlock(): 释放互斥锁
    pthread_cond_init():初始化条件变量
    pthread_cond_destroy():销毁条件变量
    pthread_cond_signal(): 唤醒第一个调用pthread_cond_wait()而进入睡眠的线程
    pthread_cond_wait(): 等待条件变量的特殊条件发生
    Thread-local storage(或者以Pthreads术语,称作线程特有数据):
    pthread_key_create(): 分配用于标识进程中线程特定数据的键
    pthread_setspecific(): 为指定线程特定数据键设置线程特定绑定
    pthread_getspecific(): 获取调用线程的键绑定,并将该绑定存储在 value 指向的位置中
    pthread_key_delete(): 销毁现有线程特定数据键
    pthread_attr_getschedparam();获取线程优先级
    pthread_attr_setschedparam();设置线程优先级
     

    Pthread工具函数

    pthread_equal(): 对两个线程的线程标识号进行比较
    pthread_detach(): 分离线程
    pthread_self(): 查询线程自身线程标识号

    2、pthread_create()线程创建

    函数简介

    pthread_create是UNIX环境创建线程函数。创建线程(实际上就是确定调用该线程函数的入口点),在线程创建以后,就开始运行相关的线程函数。

    头文件

    #include<pthread.h>

    函数声明

    int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);

    返回值

    若成功则返回0,否则返回出错编号,返回-1。

    参数

                  第一个参数为指向线程标识符的指针。

                  第二个参数用来设置线程属性。

                  第三个参数是线程运行函数的起始地址。

                  最后一个参数是运行函数的参数。

    另外

    在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库

     

        举例:

    /*thread.c*/
    #include <stdio.h>
    #include <pthread.h>
    
    /*线程一*/
    void thread_1(void)
    {
        int i=0;
        for(i=0;i<=6;i++)
        {
            printf("This is a pthread_1.\n");
            if(i==2)
                pthread_exit(0);
            sleep(1);
        }
    }
    
    /*线程二*/
    void thread_2(void)
    {
        int i;
        for(i=0;i<3;i++)
            printf("This is a pthread_2.\n");
        pthread_exit(0);
    }
    
    int main(void)
    {
        pthread_t id_1,id_2;
        int i,ret;
    /*创建线程一*/
        ret=pthread_create(&id_1,NULL,(void  *) thread_1,NULL);
        if(ret!=0)
        {
            printf("Create pthread error!\n");
        return -1;
        }
    /*创建线程二*/
         ret=pthread_create(&id_2,NULL,(void  *) thread_2,NULL);
        if(ret!=0)
        {
            printf("Create pthread error!\n");
        return -1;
        }
    /*等待线程结束*/
        pthread_join(id_1,NULL);
        pthread_join(id_2,NULL);
        return 0;
    }

        以下是程序运行结果:

        备注:pthread库不是Linux系统默认的库,连接时需要使用静态库libpthread.a,所以线程函数在编译时,需要连接库函数,如上图    gcc pthread_create.c -o pthread_create -lpthread

     

    3、pthread_join函数

    函数简介

    函数pthread_join用来等待一个线程的结束。

    函数原型为:

    extern int pthread_join __P (pthread_t __th, void **__thread_return);

    参数:

    第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。如果执行成功,将返回0,如果失败则返回一个错误号。

    例子:

    #include<stdio.h>

    #include<stdlib.h>

    #include<pthread.h>

    struct member

    {

            int num;

            char *name;

    };     

    //结构体后的分号勿漏

    void *create(void *arg)       

    //有void* 型参数传入,不能直接void

    {

            struct member *temp;

            temp=(struct member *)arg;      

    //结构体变量之间不能直接赋值,但可以通过指针赋地址

            printf("member->num:%d\n",temp->num);

            printf("member->name:%s\n",temp->name);

            sleep(1);

            return (void *)8;     

    //这个很有特色,返回一个指向void的数据类型的值,这个值作为后面的exit code

    }

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

    {

            pthread_t tidp;

            struct member *b;

            void* a;

            b=(struct member *)malloc(sizeof(struct member));           

    //先分配内存空间撒~

            b->num=1;

            b->name="mlq";              

    //字符串赋值,其他好用简便的方法有:   char *p = NULL;   p = new char [256];

            if((pthread_create(&tidp,NULL,create,(void*)b))==-1)     /

    //

    void *

    为“无类型指针”,void *  可以指向任何类型的数据

            {

                    printf("create error!\n");

                    return 1;

            }

     

    4、pthread_detach()函数

    pthread_detach(pthread_self())
    linux线程执行和windows不同,pthread有两种状态joinable状态和unjoinable状态。创建一个线程默认的状态是joinable。


    如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。但是调用pthread_join(pthread_id)后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。
    若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。


    unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己,如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。或者将线程置为joinable,然后适时调用pthread_join.

    其实简单的说就是在线程函数头加上 pthread_detach(pthread_self())的话,线程状态改变,在函数尾部直接pthread_exit线程就会自动退出。省去了给线程擦屁股的麻烦

    eg:

     pthread_t tid;
     int status = pthread_create(&tid, NULL, ThreadFunc, NULL);
     if(status != 0)
     {
      perror("pthread_create error");
     }
     pthread_detach(tid);

     

    如在Web服务器中当主线程为每个新来的链接创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的链接),这时可以在子线程中加入代码

    pthread_detach(pthread_self())
    或者父线程调用
    pthread_detach(thread_id)(非阻塞,可立即返回)
    这将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。

     

    5、线程终止

    a. 任一线程调用exit, _Exit, _exit都将导致整个进程终止;

    b. 单个线程退出方式有三种:

      1> 线程执行函数start_rtn()中使用return返回,返回值为线程退出码;

      2> 被同一个进程的其他线程使用pthread_cancel()取消;

      3> 线程自身调用了pthread_exit();

    说明:pthread_join(pthread_t tid, void **rval_ptr)函数会阻塞调用线程,直到tid线程通过上述三种方式终止退出,且return/pthread_exit()方式会设置相应线程退出码rval_ptr,而pthread_cancel()取消的线程,将退出码设置为PTHREAD_CANCELED.

     

    6、 线程清理处理程序(thread cleanup handler)

    a> pthread_cleanup_push()与pthread_cleanup_pop()均为<pthread.h>中实现的宏定义,具体实现如下:

    pthread_cleanup_push and pthread_cleanup_pop are macros and must always
       be used in matching pairs at the same nesting level of braces.  */
    #  define pthread_cleanup_push(routine, arg) \
      do{                                        \
        __pthread_cleanup_class __clframe (routine, arg)
      
    /* Remove a cleanup handler installed by the matching pthread_cleanup_push.
       If EXECUTE is non-zero, the handler function is called. */
    #  define pthread_cleanup_pop(execute) \
        __clframe.__setdoit (execute);                        \
      }while(0)

     可见push/pop中的{/}是一一对应的,因此pthread_cleanup_push/pop()也应一一对应出现,否则编译出错。

     

    b> 当线程执行下列之一操作时调用清理函数,thread_cleanup_push由栈结构实现,注意清理程序调用的顺序,先入后出。

      1: 调用pthread_exit()时,而直接return不会出发清理函数;

      2: 相应取消请求pthread_cancel()时;

      3: 使用非零execute参数调用pthread_cleanup_pop()时;

    尤其需注意pthread_cleanup_pop()参数不同及此语句所处位置不同而有不同效果。

    看此代码实例,注意return或pthread_exit()位置不同导致pthread_cleanup_pop()不同参数的效果变化。

    #include <pthread.h>
    void testPointerSize()
    {
        void*tret;
        printf("size of pointer in x86-64:%d\n",sizeof(tret));  
        //result is 8 in x86-64.
        //which is 4 in x86-32.
      
        printf("size of int in x86-64:%d\n",sizeof(int));   
        //result is 4 in x86-64.
        //which is also 4 in x86-32.
    }
    voidcleanup(void*arg)
    {
        printf("cleanup:%s\n",(char*)arg);
    }
    void* thr_fn1(void*arg)
    {
        printf("thread 1 start\n");
        pthread_cleanup_push(cleanup,"thread 1 first handler");
        pthread_cleanup_push(cleanup,"thread 1 second handler");
        if(arg)
            return((void*)1);//arg !=0 ,return here.
    //  return here will not triger any cleanup.
        pthread_cleanup_pop(0);
        pthread_cleanup_pop(1);
        return((void*)2);//will not run this
    }
    void* thr_fn2(void*arg)
    {
        printf("thread 2 start\n");
        pthread_cleanup_push(cleanup,"thread 2 first handler");
        pthread_cleanup_push(cleanup,"thread 2 second handler");
        pthread_cleanup_pop(0);
        pthread_cleanup_pop(1);
        return((void*)2);
    //  return here can triger cleanup second handler;
    }
      
    void* thr_fn3(void*arg)
    {
        printf("thread 3 start\n");
        pthread_cleanup_push(cleanup,"thread 3 first handler");
        pthread_cleanup_push(cleanup,"thread 3 second handler");
        if(arg)
            pthread_exit((void*)3);
        //pthread_exit() here will triger both cleanup first&second handler.
        pthread_cleanup_pop(1);
        pthread_cleanup_pop(0);
        pthread_exit((void*)3);//wont run this
    }
    void* thr_fn4(void*arg)
    {
        printf("thread 4 start\n");
        pthread_cleanup_push(cleanup,"thread 4 first handler");
        pthread_cleanup_push(cleanup,"thread 4 second handler");
        pthread_cleanup_pop(1);
        pthread_cleanup_pop(0);
        pthread_exit((void*)4);
        //pthread_exit() here will triger cleanup second handler.
    }
      
    int main(void)
    {
        testPointerSize();
        interr;
        pthread_t tid1, tid2, tid3, tid4;
        void*tret;
          
        err = pthread_create(&tid1, NULL, thr_fn1, (void*)1);
        err = pthread_join(tid1,&tret); 
        printf("thread 1 exit code %d\n",(int)tret);
          
        err = pthread_create(&tid2, NULL, thr_fn2, (void*)2);
        err = pthread_join(tid2, &tret);
        printf("thread 2 exit code %d\n",(int)tret);
      
        err = pthread_create(&tid3, NULL, thr_fn3, (void*)3);
        err = pthread_join(tid3,&tret); 
        printf("thread 3 exit code %d\n",(int)tret);
          
        err = pthread_create(&tid4, NULL, thr_fn4, (void*)4);
        err = pthread_join(tid4, &tret);
        printf("thread 4 exit code %d\n",(int)tret);
    }

     运行结果:

    [root@hello testData]# ./test 
    size of pointer in x86-64:8
    size of intin x86-64:4
    thread1 start
    thread1exit code 1
    thread2 start
    cleanup:thread2 first handler
    thread2exit code 2
    thread3 start
    cleanup:thread3 second handler
    cleanup:thread3 first handler
    thread3exit code 3
    thread4 start
    cleanup:thread4 second handler
    thread4exit code 4

      由上述测试程序总结如下:

    1> push与pop间的return,将导致清理程序不被触发;

    2> 位于pop之后return,由pop的参数确定是否触发清理程序,非零参数触发,零参数不触发;

    3> push/pop间的pthread_exit(),将触发所有清理函数;

    4>位于pop之后的pthread_exit()时,pop参数决定是否触发清理程序;

    其实,上述四种情况只是测试验证了前文b所说三个条件,加深理解。

     

    7、pthread_cleanup_push()/pthread_cleanup_pop()的详解

    一般来说,Posix的线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。

    不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。

    最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。

    在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源 --从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用 pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。API定义如下:

    void pthread_cleanup_push(void (*routine) (void  *),  void *arg)
    void pthread_cleanup_pop(int execute)
    

    pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。

    pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:

    #define pthread_cleanup_push(routine,arg)                                     
      { struct _pthread_cleanup_buffer _buffer;                                   
        _pthread_cleanup_push (&_buffer, (routine), (arg));
    #define pthread_cleanup_pop(execute)                                          
        _pthread_cleanup_pop (&_buffer, (execute)); }
    可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。
    work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。
    pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
    pthread_mutex_lock(&mut);
    /* do some work */
    pthread_mutex_unlock(&mut);
    pthread_cleanup_pop(0);
    必须要注意的是,如果线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在
    pthread_cleanup_push()和pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,从而导致清理函数unlock一个并没有加锁的
    mutex变量,造成错误。因此,在使用清理函数的时候,都应该暂时设置成PTHREAD_CANCEL_DEFERRED模式。为此,POSIX的
    Linux实现中还提供了一对不保证可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()扩展函数,功能与以下
    代码段相当:
    { int oldtype;
     pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
     pthread_cleanup_push(routine, arg);
     ...
     pthread_cleanup_pop(execute);
     pthread_setcanceltype(oldtype, NULL);
     }
    展开全文
  • Linux Pthread

    千次阅读 2014-08-26 16:43:05
    PTHREAD

    http://blog.csdn.net/hudashi/article/details/7709413

    http://linux.chinaitlab.com/administer/963752.html

    http://www.cnblogs.com/hoys/archive/2011/07/06/2098931.html

    http://blog.csdn.net/yetyongjin/article/details/7673837


    //2014-8-26 16:52:42  PTHREAD

    sysctl -a |grep thread

    ulimit -a   //查看当前堆栈大小,默认8M  32bit系统最大识别4G,每个进程的虚拟内存是4G,4G/8M~382个线程

    ulimit -s 1024  (k)   
    //pthread_create创建大量线程时 Resource temporarily unavailable

    pthread_attr_getstacksize(&attr, &stack_size);

    为了能创建更多线程,第一条路是创建多进程,第二条路是减少线程栈的大小。

    pthread_join SIGSEGV 段错误  
    if(pthread_kill(pthread_handle, 1)!=0), 结果是SIGHUP错误。
    没辙,加了一个死循环:  while(flag) {usleep(5000);}

    //http://blog.csdn.net/yi_jun_jun/article/details/2890934


    遗留问题:假如循环创建线程,达到上限必出现Resource temporarily unavailable,线程的虚拟内存如何释放。



    //2014-8-27 10:38:55

    ulimit -s 1024*1024命令,将线程栈大小临时设置成1M,经过试验能同时创建2000个线程了。
    有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数
    (即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。
    /etc/security/limits.d/90-nproc.conf 
    pthread_attr_setscope
    功能:        设置线程 __scope 属性。scope属性表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。
    POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,
    后者表示仅与同进程中的线程竞争CPU。默认为PTHREAD_SCOPE_PROCESS。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。

    /etc/security/limits.conf

    int pthread_setschedparam(pthread_t target_thread, int policy, const struct sched_param *param)
    在多线程开发中经常被使用的,它主要用于设置线程的调用策略和优先级。


    http://blog.csdn.net/gpengtao/article/details/7792860
    pthread_attr_init 线程属性
    linux进程调度(SCHED_OTHER,SCHED_FIFO,SCHED_RR)linux内核的三种调度方法:
    1,SCHED_OTHER 分时调度策略;
    2,SCHED_FIFO实时调度策略,先到先服务;
    3,SCHED_RR实时调度策略,时间片轮转 ;
    实时进程将得到优先调用,实时进程根据实时优先级决定调度权值,分时进程则通过nice和counter值决定权值,nice越小,counter越大,被调度的概率越大,也就是曾经使用了cpu最少的进程将会得到优先调度。
    SHCED_RR和SCHED_FIFO的不同:
    当采用SHCED_RR策略的进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。 
    SCHED_FIFO一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃。
    如果有相同优先级的实时进程(根据优先级计算的调度权值是一样的)已经准备好,FIFO时必须等待该进程主动放弃后才可以运行这个优先级相同的任务。而RR可以让每个任务都执行一段时间。
    相同点:
    RR和FIFO都只用于实时任务。
    创建时优先级大于0(1-99)。
    按照可抢占优先级调度算法进行。
    就绪态的实时任务立即抢占非实时任务。


    系统中既有分时调度,又有时间片轮转调度和先进先出调度
    1,RR调度和FIFO调度的进程属于实时进程,以分时调度的进程是非实时进程。
    2,当实时进程准备就绪后,如果当前cpu正在运行非实时进程,则实时进程立即抢占非实时进程。
    3,RR进程和FIFO进程都采用实时优先级做为调度的权值标准,RR是FIFO的一个延伸。FIFO时,如果两个进程的优先级一样,则这两个优先级一样的进程具体执行哪一个是由其在队列中的位置决定的,这样导致一些不公正性(优先级是一样的,为什么要让你一直运行?),如果将两个优先级一样的任务的调度策略都设为RR,则保证了这两个任务可以循环执行,保证了公平。


    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);

     err = pthread_create(&th1,NULL,thr,NULL);
     pthread_cancel(th1);
     pthread_join(th1,NULL);
     
    pthread_cleanup_push(pthread_mutex_unlock, (void*) &mut);
    pthread_mutex_lock(&mut);
    /* do some work */
    pthread_mutex_unlock(&mut);
    pthread_cleanup_pop(0); 
     
     
     在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数
     对用于自动释放资源--从pthread_cleanup_push()的调用点到pthread_cleanup_pop()
     之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)
     都将执行pthread_cleanup_push()所指定的清理函数。



    展开全文
  • pthread详解

    万次阅读 多人点赞 2019-09-02 11:40:30
    在Linux下创建的线程的API接口是pthread_create(),它的完整定义是: int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg); 函数参数: 1. 线程句柄 ...

    原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处 、作者信息和本声明。否则将追究法律责 
    http://blog.csdn.net/jiajun2001/article/details/12624923

    我并不假定你会使用Linux的线程,所以在这里就简单的介绍一下。如果你之前有过多线程方面的编程经验,完全可以忽略本文的内容,因为它非常的初级。

    首先说明一下,在Linux编写多线程程序需要包含头文件

    #include <pthread.h> 

    当然,进包含一个头文件是不能搞定线程的,还需要连接libpthread.so这个库,因此在程序链接阶段应该有类似 
    gcc program.o -o program -lpthread

    第一个例子

    在Linux下创建的线程的API接口是pthread_create(),它的完整定义是:

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

    函数参数: 
    1. 线程句柄 thread:当一个新的线程调用成功之后,就会通过这个参数将线程的句柄返回给调用者,以便对这个线程进行管理。 
    2. 入口函数 start_routine(): 当你的程序调用了这个接口之后,就会产生一个线程,而这个线程的入口函数就是start_routine()。如果线程创建成功,这个接口会返回0。 
    3. 入口函数参数 *arg : start_routine()函数有一个参数,这个参数就是pthread_create的最后一个参数arg。这种设计可以在线程创建之前就帮它准备好一些专有数据,最典型的用法就是使用C++编程时的this指针。start_routine()有一个返回值,这个返回值可以通过pthread_join()接口获得。 
    4. 线程属性 attr: pthread_create()接口的第二个参数用于设置线程的属性。这个参数是可选的,当不需要修改线程的默认属性时,给它传递NULL就行。具体线程有那些属性,我们后面再做介绍。

    好,那么我们就利用这些接口,来完成在Linux上的第一个多线程程序,见代码1所示:

    代码1: 第一个多线程编程范例

    #include <stdio.h>  
    #include <pthread.h>  
    void* thread( void *arg )  
    {  
        printf( "This is a thread and arg = %d.\n", *(int*)arg);  
        *(int*)arg = 0;  
        return arg;  
    }  
    int main( int argc, char *argv[] )  
    {  
        pthread_t th;  
        int ret;  
        int arg = 10;  
        int *thread_ret = NULL;  
        ret = pthread_create( &th, NULL, thread, &arg );  
        if( ret != 0 ){  
            printf( "Create thread error!\n");  
            return -1;  
        }  
        printf( "This is the main process.\n" );  
        pthread_join( th, (void**)&thread_ret );  
        printf( "thread_ret = %d.\n", *thread_ret );  
        return 0;  
    }  

    将这段代码保存为thread.c文件,可以执行下面的命令来生成可执行文件: 
    $ gcc thread.c -o thread -lpthread 
    这段代码的执行结果可能是这样:

    $ ./thread
    This is the main process.
    This is a thread and arg = 10.
    thread_ret = 0.

    注意,我说的是可能有这样的结果,在不同的环境下可能会有出入。因为这是多线程程序,线程代码可能先于第24行代码被执行。

    代码分析: 
    - 在第18行调用pthread_create()接口创建了一个新的线程,这个线程的入口函数是thread(),并且给这个入口函数传递了一个参数,且参数值为10。 
    - 这个新创建的线程要执行的任务非常简单,只是将显示“This is a thread and arg = 10”这个字符串,因为arg这个参数值已经定义好了,就是10。之后线程将arg参数的值修改为0,并将它作为线程的返回值返回给系统。

    pthread_join()这个接口的第一个参数就是新创建线程的句柄了,而第二个参数就会去接受线程的返回值。pthread_join()接口会阻塞主进程的执行,直到合并的线程执行结束。由于线程在结束之后会将0返回给系统,那么pthread_join()获得的线程返回值自然也就是0。输出结果“thread_ret = 0”也证实了这一点。

    那么现在有一个问题,那就是pthread_join()接口干了什么?什么是线程合并呢?

    线程的合并与分离

    线程的合并

    我们首先要明确的一个问题就是什么是线程的合并。从前面的叙述中读者们已经了解到了,pthread_create()接口负责创建了一个线程。那么线程也属于系统的资源,这跟内存没什么两样,而且线程本身也要占据一定的内存空间。

    众所周知的一个问题就是C/C++编程中如果要通过malloc()new分配了一块内存,就必须使用free()delete来回收这块内存,否则就会产生著名的内存泄漏问题

    既然线程和内存没什么两样,那么有创建就必须得有回收,否则就会产生另外一个著名的资源泄漏问题,这同样也是一个严重的问题。那么线程的合并就是回收线程资源了。

    线程的合并是一种主动回收线程资源的方案。当一个进程或线程调用了针对其它线程的pthread_join()接口,就是线程合并了。这个接口会阻塞调用进程或线程,直到被合并的线程结束为止。当被合并线程结束,pthread_join()接口就会回收这个线程的资源,并将这个线程的返回值返回给合并者。

    线程的分离

    与线程合并相对应的另外一种线程资源回收机制是线程分离,调用接口是pthread_detach()

    线程分离是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。因为线程分离是启动系统的自动回收机制,那么程序也就无法获得被分离线程的返回值,这就使得pthread_detach()接口只要拥有一个参数就行了,那就是被分离线程句柄。

    线程合并和线程分离都是用于回收线程资源的,可以根据不同的业务场景酌情使用。不管有什么理由,你都必须选择其中一种,否则就会引发资源泄漏的问题,这个问题与内存泄漏同样可怕。

    线程的属性

    前面还说到过线程是有属性的,这个属性由一个线程属性对象来描述。线程属性对象由pthread_attr_init()接口初始化,并由pthread_attr_destory()来销毁,它们的完整定义是:

    int pthread_attr_init(pthread_attr_t *attr);  
    int pthread_attr_destory(pthread_attr_t *attr);  

    线程拥有哪些属性呢? 
    一般地,Linux下的线程有:绑定属性、分离属性、调度属性、堆栈大小属性和满占警戒区大小属性。

    下面我们就分别来介绍这些属性。

    绑定属性

    说到这个绑定属性,就不得不提起另外一个概念:轻进程(Light Weight Process,简称LWP)。

    轻进程和Linux系统的内核线程拥有相同的概念,属于内核的调度实体。一个轻进程可以控制一个或多个线程。

    在计算机操作系统中,轻量级进程(LWP)是一种实现多任务的方法。与普通进程相比,LWP与其他进程共享所有(或大部分)它的逻辑地址空间和系统资源;与线程相比,LWP有它自己的进程标识符,并和其他进程有着父子关系;这是和类Unix操作系统的系统调用vfork()生成的进程一样的。另外,线程既可由应用程序管理,又可由内核管理,而LWP只能由内核管理并像普通进程一样被调度。Linux内核是支持LWP的典型例子。

    默认情况下,对于一个拥有n个线程的程序,启动多少轻进程,由哪些轻进程来控制哪些线程由操作系统来控制,这种状态被称为非绑定的。那么绑定的含义就很好理解了,只要指定了某个线程“绑”在某个轻进程上,就可以称之为绑定的了。

    被绑定的线程具有较高的相应速度,因为操作系统的调度主体是轻进程,绑定线程可以保证在需要的时候它总有一个轻进程可用。绑定属性就是干这个用的。

    设置绑定属性的接口是pthread_attr_setscope(),它的完整定义是:

    int pthread_attr_setscope(pthread_attr_t *attr, int scope);

    它有两个参数,第一个就是线程属性对象的指针,第二个就是绑定类型,拥有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。代码2演示了这个属性的使用。

    代码2 : 设置线程绑定属性

    #include <stdio.h>  
    #include <pthread.h>  
    ……  
    int main( int argc, char *argv[] )  
    {  
        pthread_attr_t attr;  
        pthread_t th;  
        ……  
        pthread_attr_init( &attr );  
        pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );  
        pthread_create( &th, &attr, thread, NULL );  
        ……  
    }  

    不知道你是否在这里发现了本文的矛盾之处。就是这个绑定属性跟我们之前说的NPTL有矛盾之处。

    在介绍NPTL的时候就说过业界有一种m:n的线程方案,就跟这个绑定属性有关。但是笔者还说过NPTL因为Linux的“蠢”没有采取这种方案,而是采用了“1:1”的方案。这也就是说,Linux的线程永远都是绑定。

    对,Linux的线程永远都是绑定的,所以PTHREAD_SCOPE_PROCESS在Linux中不管用,而且会返回ENOTSUP错误。

    既然Linux并不支持线程的非绑定,为什么还要提供这个接口呢?答案就是兼容!因为Linux的NTPL是号称POSIX标准兼容的,而绑定属性正是POSIX标准所要求的,所以提供了这个接口。如果读者们只是在Linux下编写多线程程序,可以完全忽略这个属性。如果哪天你遇到了支持这种特性的系统,别忘了我曾经跟你说起过这玩意儿:)

    分离属性

    前面说过线程能够被合并和分离,分离属性就是让线程在创建之前就决定它应该是分离的。如果设置了这个属性,就没有必要调用pthread_join()pthread_detach()来回收线程资源了。 
    设置分离属性的接口是pthread_attr_setdetachstate(),它的完整定义是:

    pthread_attr_setdetachstat(pthread_attr_t *attr, int detachstate); 

    它的第二个参数有两个取值:PTHREAD_CREATE_DETACHED(分离的)和PTHREAD_CREATE_JOINABLE(可合并的,也是默认属性)。代码3演示了这个属性的使用。

    代码3 : 设置线程分离属性

    #include <stdio.h>  
    #include <pthread.h>  
    ……  
    int main( int argc, char *argv[] )  
    {  
        pthread_attr_t attr;  
        pthread_t th;  
        ……  
        pthread_attr_init( &attr );  
        pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );  
        pthread_create( &th, &attr, thread, NULL );  
        ……  
    } 

    调度属性

    线程的调度属性有三个,分别是:算法、优先级和继承权。

    算法

    Linux提供的线程调度算法有三个:轮询、先进先出和其它。

    其中轮询和先进先出调度算法是POSIX标准所规定,而其他则代表采用Linux自己认为更合适的调度算法,所以默认的调度算法也就是其它了。 
    轮询和先进先出调度算法都属于实时调度算法

    • 轮询指的是时间片轮转,当线程的时间片用完,系统将重新分配时间片,并将它放置在就绪队列尾部,这样可以保证具有相同优先级的轮询任务获得公平的CPU占用时间;
    • 先进先出就是先到先服务,一旦线程占用了CPU则一直运行,直到有更高优先级的线程出现或自己放弃。

    设置线程调度算法的接口是pthread_attr_setschedpolicy(),它的完整定义是:

    pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

    它的第二个参数有三个取值:SCHED_RR(轮询)、SCHED_FIFO(先进先出)和SCHED_OTHER(其它)。

    优先级

    Linux的线程优先级与进程的优先级不一样,进程优先级我们后面再说。

    Linux的线程优先级是从1到99的数值,数值越大代表优先级越高。 
    而且要注意的是,只有采用SHCED_RR或SCHED_FIFO调度算法时,优先级才有效。对于采用SCHED_OTHER调度算法的线程,其优先级恒为0。

    设置线程优先级的接口是pthread_attr_setschedparam(),它的完整定义是:

    struct sched_param {  
        int sched_priority;  
    }  
    int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param);  

    sched_param结构体的sched_priority字段就是线程的优先级了。

    此外,即便采用SCHED_RRSCHED_FIFO调度算法,线程优先级也不是随便就能设置的。首先,进程必须是以root账号运行的;其次,还需要放弃线程的继承权。什么是继承权呢?

    继承权

    继承权就是当创建新的线程时,新线程要继承父线程(创建者线程)的调度属性。如果不希望新线程继承父线程的调度属性,就要放弃继承权。 
    设置线程继承权的接口是pthread_attr_setinheritsched(),它的完整定义是:

    int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);  

    它的第二个参数有两个取值:PTHREAD_INHERIT_SCHED(拥有继承权)和PTHREAD_EXPLICIT_SCHED(放弃继承权)。新线程在默认情况下是拥有继承权。 
    代码4能够演示不同调度算法和不同优先级下各线程的行为,同时也展示如何修改线程的调度属性。

    代码4:设置线程调度属性

    #include <stdio.h>  
    #include <unistd.h>  
    #include <stdlib.h>  
    #include <pthread.h>  
    #define THREAD_COUNT 12  
    void show_thread_policy( int threadno )  
    {  
        int policy;  
        struct sched_param param;  
        pthread_getschedparam( pthread_self(), &policy, param );  
        switch( policy ){  
        case SCHED_OTHER:  
            printf( "SCHED_OTHER %d\n", threadno );  
            break;  
        case SCHED_RR:  
            printf( "SCHDE_RR %d\n", threadno );  
            break;  
        case SCHED_FIFO:  
            printf( "SCHED_FIFO %d\n", threadno );  
            break;  
        default:  
            printf( "UNKNOWN\n");  
        }  
    }  
    void* thread( void *arg )  
    {  
        int i, j;  
        long threadno = (long)arg;  
        printf( "thread %d start\n", threadno );  
        sleep(1);  
        show_thread_policy( threadno );  
        for( i = 0; i < 10; ++i ) {  
            for( j = 0; j < 100000000; ++j ){}  
            printf( "thread %d\n", threadno );  
        }  
        printf( "thread %d exit\n", threadno );  
        return NULL;  
    }  
    int main( int argc, char *argv[] )  
    {  
        long i;  
        pthread_attr_t attr[THREAD_COUNT];  
        pthread_t pth[THREAD_COUNT];  
        struct sched_param param;  
        for( i = 0; i < THREAD_COUNT; ++i )  
            pthread_attr_init( &attr[i] );  
            for( i = 0; i < THREAD_COUNT / 2; ++i ) {  
                param.sched_priority = 10;                    
                pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );  
                pthread_attr_setschedparam( &attr[i], param );  
                pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );  
            }  
            for( i = THREAD_COUNT / 2; i < THREAD_COUNT; ++i ) {  
                param.sched_priority = 20;                    
                pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );  
                pthread_attr_setschedparam( &attr[i], param );  
                pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );  
            }  
            for( i = 0; i < THREAD_COUNT; ++i )                      
                pthread_create( &pth[i], &attr[i], thread, (void*)i );                
            for( i = 0; i < THREAD_COUNT; ++i )                      
                pthread_join( pth[i], NULL );                      
            for( i = 0; i < THREAD_COUNT; ++i )                      
                pthread_attr_destroy( &attr[i] );                     
        return 0;                             
    }  

    这段代码中含有一些没有介绍过的接口,读者们可以使用Linux的联机帮助来查看它们的具体用法和作用。

    堆栈大小属性

    从前面的这些例子中可以了解到,线程的主函数与程序的主函数main()有一个很相似的特性,那就是可以拥有局部变量。虽然同一个进程的线程之间是共享内存空间的,但是它的局部变量确并不共享。原因就是局部变量存储在堆栈中,而不同的线程拥有不同的堆栈。Linux系统为每个线程默认分配了8MB的堆栈空间,如果觉得这个空间不够用,可以通过修改线程的堆栈大小属性进行扩容。 
    修改线程堆栈大小属性的接口是pthread_attr_setstacksize(),它的完整定义为:

    int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);  

    它的第二个参数就是堆栈大小了,以字节为单位。需要注意的是,线程堆栈不能小于16KB,而且尽量按4KB(32位系统)或2MB(64位系统)的整数倍分配,也就是内存页面大小的整数倍。此外,修改线程堆栈大小是有风险的,如果你不清楚你在做什么,最好别动它(其实我很后悔把这么危险的东西告诉了你:)。

    满栈警戒区属性

    既然线程是有堆栈的,而且还有大小限制,那么就一定会出现将堆栈用满的情况。线程的堆栈用满是非常危险的事情,因为这可能会导致对内核空间的破坏,一旦被有心人士所利用,后果也不堪设想。为了防治这类事情的发生,Linux为线程堆栈设置了一个满栈警戒区。这个区域一般就是一个页面,属于线程堆栈的一个扩展区域。一旦有代码访问了这个区域,就会发出SIGSEGV信号进行通知。

    虽然满栈警戒区可以起到安全作用,但是也有弊病,就是会白白浪费掉内存空间,对于内存紧张的系统会使系统变得很慢。所有就有了关闭这个警戒区的需求。同时,如果我们修改了线程堆栈的大小,那么系统会认为我们会自己管理堆栈,也会将警戒区取消掉,如果有需要就要开启它。 
    修改满栈警戒区属性的接口是pthread_attr_setguardsize(),它的完整定义为:

    int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);  

    它的第二个参数就是警戒区大小了,以字节为单位。与设置线程堆栈大小属性相仿,应该尽量按照4KB或2MB的整数倍来分配。当设置警戒区大小为0时,就关闭了这个警戒区。 
    虽然栈满警戒区需要浪费掉一点内存,但是能够极大的提高安全性,所以这点损失是值得的。而且一旦修改了线程堆栈的大小,一定要记得同时设置这个警戒区。

    线程本地存储

    内线程之间可以共享内存地址空间,线程之间的数据交换可以非常快捷,这是线程最显著的优点。但是多个线程访问共享数据,需要昂贵的同步开销,也容易造成与同步相关的BUG,更麻烦的是有些数据根本就不希望被共享,这又是缺点。可谓:“成也萧何,败也萧何”,说的就是这个道理。

    C程序库中的errno是个最典型的一个例子。errno是一个全局变量,会保存最后一个系统调用的错误代码。在单线程环境并不会出现什么问题。但是在多线程环境,由于所有线程都会有可能修改errno,这就很难确定errno代表的到底是哪个系统调用的错误代码了。这就是有名的“非线程安全(Non Thread-Safe)”的。

    此外,从现代技术角度看,在很多时候使用多线程的目的并不是为了对共享数据进行并行处理(在Linux下有更好的方案,后面会介绍)。更多是由于多核心CPU技术的引入,为了充分利用CPU资源而进行并行运算(不互相干扰)。换句话说,大多数情况下每个线程只会关心自己的数据而不需要与别人同步。

    为了解决这些问题,可以有很多种方案。比如使用不同名称的全局变量。但是像errno这种名称已经固定了的全局变量就没办法了。在前面的内容中提到在线程堆栈中分配局部变量是不在线程间共享的。但是它有一个弊病,就是线程内部的其它函数很难访问到。

    目前解决这个问题的简便易行的方案是线程本地存储,即Thread Local Storage,简称TLS。利用TLS,errno所反映的就是本线程内最后一个系统调用的错误代码了,也就是线程安全的了。 
    Linux提供了对TLS的完整支持,通过下面这些接口来实现:

    int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));  
    int pthread_key_delete(pthread_key_t key);  
    void* pthread_getspecific(pthread_key_t key);  
    int pthread_setspecific(pthread_key_t key, const void *value); 

    pthread_key_create()接口用于创建一个线程本地存储区。 
    - key : 第一个参数用来返回这个存储区的句柄,需要使用一个全局变量保存,以便所有线程都能访问到。 
    - *destructor : 第二个参数是线程本地数据的回收函数指针,如果希望自己控制线程本地数据的生命周期,这个参数可以传递NULL。

    pthread_key_delete()接口用于回收线程本地存储区。其唯一的参数就要回收的存储区的句柄。

    pthread_getspecific()和pthread_setspecific()这个两个接口分别用于获取和设置线程本地存储区的数据。这两个接口在不同的线程下会有不同的结果不同(相同的线程下就会有相同的结果),这也就是线程本地存储的关键所在。

    代码5展示了如何在Linux使用线程本地存储,注意执行结果,分析一下线程本地存储的一些特性,以及内存回收的时机。

    代码5 :使用线程本地存储

    #include <stdio.h>  
    #include <stdlib.h>  
    #include <pthread.h>  
    #define THREAD_COUNT 10  
    pthread_key_t g_key;  
    typedef struct thread_data{  
        int thread_no;  
    } thread_data_t;  
    void show_thread_data()  
    {  
        thread_data_t *data = pthread_getspecific( g_key );  
        printf( "Thread %d \n", data->thread_no );  
    }  
    void* thread( void *arg )  
    {  
        thread_data_t *data = (thread_data_t *)arg;  
        printf( "Start thread %d\n", data->thread_no );  
        pthread_setspecific( g_key, data );  
        show_thread_data();  
        printf( "Thread %d exit\n", data->thread_no );  
    }  
    void free_thread_data( void *arg )  
    {  
        thread_data_t *data = (thread_data_t*)arg;  
        printf( "Free thread %d data\n", data->thread_no );  
        free( data );  
    }  
    int main( int argc, char *argv[] )  
    {  
        int i;  
        pthread_t pth[THREAD_COUNT];  
        thread_data_t *data = NULL;  
        pthread_key_create( &g_key, free_thread_data );  
        for( i = 0; i < THREAD_COUNT; ++i ) {  
            data = malloc( sizeof( thread_data_t ) );  
            data->thread_no = i;  
            pthread_create( &pth[i], NULL, thread, data );  
        }  
        for( i = 0; i < THREAD_COUNT; ++i )  
            pthread_join( pth[i], NULL );  
        pthread_key_delete( g_key );  
        return 0;  
    } 

    线程的同步

    虽然线程本地存储可以避免线程访问共享数据,但是线程之间的大部分数据始终还是共享的。在涉及到对共享数据进行读写操作时,就必须使用同步机制,否则就会造成线程们哄抢共享数据的结果,这会把你的数据弄的七零八落理不清头绪。 
    Linux提供的线程同步机制主要有互斥锁条件变量。其它形式的线程同步机制用得并不多,本书也不准备详细讲解,有兴趣的读者可以参考相关文档。

    互斥锁

    首先我们看一下互斥锁。所谓的互斥就是线程之间互相排斥,获得资源的线程排斥其它没有获得资源的线程。Linux使用互斥锁来实现这种机制。

    既然叫锁,就有加锁和解锁的概念。当线程获得了加锁的资格,那么它将独享这个锁,其它线程一旦试图去碰触这个锁就立即被系统“拍晕”。当加锁的线程解开并放弃了这个锁之后,那些被“拍晕”的线程会被系统唤醒,然后继续去争抢这个锁。至于谁能抢到,只有天知道。但是总有一个能抢到。于是其它来凑热闹的线程又被系统给“拍晕”了……如此反复。感觉线程的“头”很痛:)

    从互斥锁的这种行为看,线程加锁和解锁之间的代码相当于一个独木桥,同意时刻只有一个线程能执行。从全局上看,在这个地方,所有并行运行的线程都变成了排队运行了。比较专业的叫法是同步执行,这段代码区域叫临界区。同步执行就破坏了线程并行性的初衷了,临界区越大破坏得越厉害。所以在实际应用中,应该尽量避免有临界区出现。实在不行,临界区也要尽量的小。如果连缩小临界区都做不到,那还使用多线程干嘛?

    互斥锁在Linux中的名字是mutex。这个似乎优点眼熟。对,在前面介绍NPTL的时候提起过,但是那个叫futex,是系统底层机制。对于提供给用户使用的则是这个mutex。Linux初始化和销毁互斥锁的接口是pthread_mutex_init()pthead_mutex_destroy()

    对于加锁和解锁则有pthread_mutex_lock()pthread_mutex_trylock()pthread_mutex_unlock()。 
    这些接口的完整定义如下:

    int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);  
    int pthread_mutex_destory(pthread_mutex_t *mutex );  
    int pthread_mutex_lock(pthread_mutex_t *mutex);  
    int pthread_mutex_trylock(pthread_mutex_t *mutex);  
    int pthread_mutex_unlock(pthread_mutex_t *mutex);  

    从这些定义中可以看到,互斥锁也是有属性的。只不过这个属性在绝大多数情况下都不需要改动,所以使用默认的属性就行。方法就是给它传递NULL。

    phtread_mutex_trylock()比较特别,用它试图加锁的线程永远都不会被系统“拍晕”,只是通过返回EBUSY来告诉程序员这个锁已经有人用了。至于是否继续“强闯”临界区,则由程序员决定。系统提供这个接口的目的可不是让线程“强闯”临界区的。它的根本目的还是为了提高并行性,留着这个线程去干点其它有意义的事情。当然,如果很幸运恰巧这个时候还没有人拥有这把锁,那么自然也会取得临界区的使用权。

    代码6演示了在Linux下如何使用互斥锁。

    代码6 : 使用互斥锁

    #include <stdio.h>  
    #include <stdlib.h>  
    #include <errno.h>  
    #include <unistd.h>  
    #include <pthread.h>  
    pthread_mutex_t g_mutex;  
    int g_lock_var = 0;  
    void* thread1( void *arg )  
    {  
        int i, ret;  
        time_t end_time;  
        end_time = time(NULL) + 10;  
        while( time(NULL) < end_time ) {  
            ret = pthread_mutex_trylock( &g_mutex );  
            if( EBUSY == ret ) {  
                printf( "thread1: the varible is locked by thread2.\n" );  
            } else {  
                printf( "thread1: lock the variable!\n" );  
                ++g_lock_var;  
                pthread_mutex_unlock( &g_mutex );  
            }  
            sleep(1);  
        }  
        return NULL;  
    }  
    void* thread2( void *arg )  
    {  
        int i;  
        time_t end_time;  
        end_time = time(NULL) + 10;  
        while( time(NULL) < end_time ) {  
            pthread_mutex_lock( &g_mutex );  
            printf( "thread2: lock the variable!\n" );  
            ++g_lock_var;  
            sleep(1);  
            pthread_mutex_unlock( &g_mutex );  
        }  
        return NULL;  
    }  
    int main( int argc, char *argv[] )  
    {  
        int i;  
        pthread_t pth1,pth2;  
        pthread_mutex_init( &g_mutex, NULL );  
        pthread_create( &pth1, NULL, thread1, NULL );  
        pthread_create( &pth2, NULL, thread2, NULL );  
        pthread_join( pth1, NULL );  
        pthread_join( pth2, NULL );  
        pthread_mutex_destroy( &g_mutex );  
        printf( "g_lock_var = %d\n", g_lock_var );  
        return 0;                              
    }  

    最后需要补充一点,互斥锁在同一个线程内,没有互斥的特性。也就是说,线程不能利用互斥锁让系统将自己“拍晕”。解释这个现象的一个很好的理由就是,拥有锁的线程把自己“拍晕”了,谁还能再拥有这把锁呢?但是另外情况需要避免,就是两个线程已经各自拥有一把锁了,但是还想得到对方的锁,这个时候两个线程都会被“拍晕”。一旦这种情况发生,就谁都不能获得这个锁了,这种情况还有一个著名的名字——死锁。死锁是永远都要避免的事情,因为这是严重损人不利己的行为。

    条件变量

    条件变量关键点在“变量”上。与锁的不同之处就是,当线程遇到这个“变量”,并不是类似锁那样的被系统给“拍晕”,而是根据“条件”来选择是否在那里等待。等待什么呢?等待允许通过的“信号”。这个“信号”是系统控制的吗?显然不是!它是由另外一个线程来控制的。

    如果说互斥锁可以比作独木桥,那么条件变量这就好比是马路上的红绿灯。车辆遇到红绿灯肯定会根据“灯”的颜色来判断是否通行,毕竟红灯停绿灯行这个道理在幼儿园的时候老师就教了。那么谁来控制“灯”的颜色呢?一定是交警啊,至少你我都不敢动它(有人会说那是自动的,可是间隔多少时间变换也是交警设置不是?)。那么“车辆”和“交警”就是马路上的两类线程,大多数情况下都是“车”多“交警”少。

    更深一步理解,条件变量是一种事件机制。由一类线程来控制“事件”的发生,另外一类线程等待“事件”的发生。为了实现这种机制,条件变量必须是共享于线程之间的全局变量。而且,条件变量也需要与互斥锁同时使用

    初始化和销毁条件变量的接口是pthread_cond_init()pthread_cond_destory()
    控制“事件”发生的接口是pthread_cond_signal()pthread_cond_broadcast(); 
    等待“事件”发生的接口是pthead_cond_wait()pthread_cond_timedwait()。 
    它们的完整定义如下:

    int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);  
    int pthread_cond_destory(pthread_cond_t *cond);  
    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);  
    int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex, const timespec *abstime);  
    int pthread_cond_signal(pthread_cond_t *cond);  
    int pthread_cond_broadcast(pthread_cond_t *cond); 

    对于等待“事件”的接口从其名称中可以看出,一种是无限期等待,一种是限时等待。后者与互斥锁的pthread_mutex_trylock()有些类似,即当等待的“事件”经过一段时间之后依然没有发生,那就去干点别的有意义的事情去。

    而对于控制“事件”发生的接口则有“单播”和“广播”之说。所谓单播就是只有一个线程会得到“事件”已经发生了的“通知”,而广播就是所有线程都会得到“通知”。对于广播情况,所有被“通知”到的线程也要经过由互斥锁控制的独木桥。

    对于条件变量的使用,可以参考代码7,它实现了一种生产者与消费者的线程同步方案。

    代码7 : 使用条件变量

    #include <stdio.h>  
    #include <stdlib.h>  
    #include <pthread.h>  
    #define BUFFER_SIZE 5  
    pthread_mutex_t g_mutex;  
    pthread_cond_t g_cond;  
    typedef struct {  
        char buf[BUFFER_SIZE];  
        int count;  
    } buffer_t;  
    buffer_t g_share = {"", 0};  
    char g_ch = 'A';  
    void* producer( void *arg )  
    {  
        printf( "Producer starting.\n" );  
        while( g_ch != 'Z' ) {  
            pthread_mutex_lock( &g_mutex );  
            if( g_share.count < BUFFER_SIZE ) {  
                g_share.buf[g_share.count++] = g_ch++;  
                printf( "Prodcuer got char[%c]\n", g_ch - 1 );  
                if( BUFFER_SIZE == g_share.count ) {  
                    printf( "Producer signaling full.\n" );  
                    pthread_cond_signal( &g_cond );  
                }  
            }  
            pthread_mutex_unlock( &g_mutex );  
        }  
        printf( "Producer exit.\n" );  
        return NULL;  
    }  
    void* consumer( void *arg )  
    {  
        int i;  
        printf( "Consumer starting.\n" );  
        while( g_ch != 'Z' ) {  
            pthread_mutex_lock( &g_mutex );  
            printf( "Consumer waiting\n" );  
            pthread_cond_wait( &g_cond, &g_mutex );  
            printf( "Consumer writing buffer\n" );  
            for( i = 0; g_share.buf[i] && g_share.count; ++i ) {  
                putchar( g_share.buf[i] );  
                --g_share.count;  
            }  
            putchar('\n');  
            pthread_mutex_unlock( &g_mutex );  
        }  
        printf( "Consumer exit.\n" );  
        return NULL;  
    }  
    int main( int argc, char *argv[] )  
    {  
        pthread_t ppth, cpth;  
        pthread_mutex_init( &g_mutex, NULL );  
        pthread_cond_init( &g_cond, NULL );  
        pthread_create( &cpth, NULL, consumer, NULL );  
        pthread_create( &ppth, NULL, producer, NULL );  
        pthread_join( ppth, NULL );  
        pthread_join( cpth, NULL );  
        pthread_mutex_destroy( &g_mutex );  
        pthread_cond_destroy( &g_cond );  
        return 0;  
    }  

    这段代码存在一个潜在的问题: 
    如果producer线程并行执行的比consumer快,producer线程会先获取锁,之后向consumer发出信号,但此时consumer没办法获取锁,也就执行不到pthead_cond_wait() 处,那么程序就陷入尴尬的境地,发生死锁。

    简单的,可以在 pthread_create( &cpth, NULL, consumer, NULL ); 和pthread_create( &ppth, NULL, producer, NULL ); 之间加入一个长的延时函数usleep(100),确保consumer线程先行执行到pthead_cond_wait() 处。

    从代码中会发现,等待“事件”发生的接口都需要传递一个互斥锁给它。而实际上这个互斥锁还要在调用它们之前加锁,调用之后解锁。不单如此,在调用操作“事件”发生的接口之前也要加锁,调用之后解锁。这就面临一个问题,按照这种方式,等于“发生事件”和“等待事件”是互为临界区的。也就是说,如果“事件”还没有发生,那么有线程要等待这个“事件”就会阻止“事件”的发生。更干脆一点,就是这个“生产者”和“消费者”是在来回的走独木桥。但是实际的情况是,“消费者”在缓冲区满的时候会得到这个“事件”的“通知”,然后将字符逐个打印出来,并清理缓冲区。直到缓冲区的所有字符都被打印出来之后,“生产者”才开始继续工作。

    为什么会有这样的结果呢?这就要说明一下pthread_cond_wait()接口对互斥锁做什么。答案是:解锁pthread_cond_wait()首先会解锁互斥锁,然后进入等待。这个时候“生产者”就能够进入临界区,然后在条件满足的时候向“消费者”发出信号。

    pthead_cond_wait()获得“通知”之后,它还要对互斥锁加锁,这样可以防止“生产者”继续工作而“撑坏”缓冲区。另外,“生产者”在缓冲区不满的情况下才能工作的这个限定条件是很有必要的。因为在pthread_cond_wait()获得通知之后,在没有对互斥锁加锁之前,“生产者”可能已经重新进入临界区了,这样“消费者”又被堵住了。也就是因为条件变量这种工作性质,导致它必须与互斥锁联合使用。

    此外,利用条件变量和互斥锁,可以模拟出很多其它类型的线程同步机制,比如:event、semaphore等。本书不再多讲,有兴趣的读者可以参考其它著作。或自己根据它们的行为自己来模拟实现。

    展开全文
  • pthread pthread_mutex pthread_cond相关

    千次阅读 2013-08-08 22:10:45
    pthread这块api有点多,没有精力去每个都弄清楚。。。。。 这里只说下pthread_mutex 和pthread_cond用法。 1,pthread_cond_wait用于阻塞当前线程,等待别的线程调用pthread_cond_signal或者pthread_cond_...
    pthread这块api有点多,没有精力去每个都弄清楚。。。。。

    这里只说下pthread_mutex 和pthread_cond用法。

    1,pthread_cond_wait用于阻塞当前线程,等待别的线程调用pthread_cond_signal或者pthread_cond_broadcast()来唤醒自己。
    2,pthread_cond_wait与pthread_mutex_  配合使用,pthread_cond_wait进入wait状态会释放mutex信号量,并等待其他线程释放自己等待的cond信号量,pthread_cond_wait返回时又会重新获得mutex。
    3,pthread_cond_signal函数发送信号给另外一个正在处于阻塞状态的线程,使其脱离阻塞状态继续执行,如果没有阻塞,该函数也会返回成功。
    4,pthread_cond_signal发送的信号只能被一个阻塞的地方使用,获取该信号要根据阻塞线程的优先级决定,优先级相同的或根据阻塞时间的长度决定。
    5,pthread_cond_broadcast会唤醒所有阻塞的线程。
    6,pthread_cond_wait一般使用while(1)做判断。

    自己先写了个小程序:


    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    
    
    static pthread_cond_t cond1;
    static pthread_cond_t cond2;
    static pthread_mutex_t test_lock = PTHREAD_MUTEX_INITIALIZER;
    
    static void *
    thread1_func(void* arg)
    {
    	while(1)
    	{
    		pthread_mutex_lock(&test_lock);
    		pthread_cond_wait(&cond1, &test_lock );
    		printf("Thread 1 done.\n");
    		pthread_mutex_unlock(&test_lock);
    		sleep(1);
    	}
    	return 0;
    }
    
    static void *
    thread2_func(void* arg)
    {
    	while(1)
    	{
    		pthread_mutex_lock(&test_lock);
    		pthread_cond_wait(&cond2, &test_lock );
    		printf("Thread 2 done.\n");
    		pthread_mutex_unlock(&test_lock);
    		sleep(1);
    	}
    	return 0;
    }
    
    static void *
    thread3_func(void* arg)
    {
    	while(1)
    	{
    		pthread_mutex_lock(&test_lock);
    		pthread_cond_wait(&cond1, &test_lock );
    		printf("Thread 3 done.\n");
    		pthread_mutex_unlock(&test_lock);
    		pthread_cond_signal(&cond2);
    		sleep(1);
    	}
    	return 0;
    }
    
    static void *
    thread4_func(void* arg)
    {
    	while(1)
    	{
    		pthread_mutex_lock(&test_lock);
    		printf("Thread 4 done.\n");
    		pthread_mutex_unlock(&test_lock);
    		pthread_cond_broadcast(&cond1);
    		sleep(1);
    	}
    	return 0;
    }
    
    int main(int argc, const char *argv[])
    {
        pthread_t t[4];
    
        pthread_cond_init(&cond1, NULL);
        pthread_cond_init(&cond2, NULL);
        pthread_create( &t[0], NULL, thread1_func, (void *)1 );
        pthread_create( &t[1], NULL, thread2_func, (void *)2 );
        pthread_create( &t[2], NULL, thread3_func, (void *)3 );
        pthread_create( &t[3], NULL, thread4_func, (void *)4 );
    
        pthread_join(t[0], NULL);
        pthread_join(t[1], NULL);
        pthread_join(t[2], NULL);
        pthread_join(t[3], NULL);
        return 0;
    }
    

    运行结果:
    Thread 4 done.

    Thread 1 done.

    Thread 3 done.

    Thread 2 done.

    Thread 4 done.

    Thread 3 done.

    Thread 2 done.

    Thread 4 done.

    Thread 1 done.

    Thread 3 done.

    Thread 4 done.

    Thread 3 done.

    Thread 2 done.

    Thread 4 done.

    Thread 1 done.

    Thread 4 done.

    Thread 3 done.

    Thread 2 done.

    Thread 4 done.

    Thread 1 done.

    Thread 4 done.

    Thread 3 done........

    4线程唤醒了1,3线程,3线程又唤醒了2线程。。。

    熟悉了api功能后然后自己写了一个生产者消费者的代码:

    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <string.h>
    
    #define FIFO_SIZE 10
    
    typedef struct 
    {
    	int Fifo[FIFO_SIZE];
    	int ReadPos;
    	int writePos;
    	pthread_mutex_t locke;
    	pthread_cond_t Nfull_Signal;
    	pthread_cond_t Nemp_Signal;
    }Product;
    
     Product product;
    
    //判断Fifo为空?
    int FifoIsEmpty(Product* Object)
    {
    	if(Object->ReadPos == Object->writePos)
    	{
    		return 1;
    	}
    	return 0;
    }
    
    //判断Fifo已满?
    int FifoIsFull(Product* Object)
    {
    	if((Object->writePos+1)%FIFO_SIZE == Object->ReadPos)
    	{
    		return 1;
    	}
    	return 0;
    }
    
    static void creat_product(Product* Object)
    {
    	while(FifoIsFull(Object))
    	{
    		printf("1 wait.......... \n");
    		pthread_cond_wait(&(Object->Nfull_Signal),&(Object->locke));
    	}
    
    	int product = 1;
    	
    	Object->Fifo[Object->writePos] = product;
    	Object->writePos = (Object->writePos+1)%FIFO_SIZE;
    	printf("1 done \n");
    	pthread_cond_signal(&(Object->Nemp_Signal));  
    }
    
    static void consume_product(Product* Object)
    {
    	while(FifoIsEmpty(Object))
    	{
    		printf("2 wait..........\n");
    		pthread_cond_wait(&(Object->Nemp_Signal), &(Object->locke));
    	}
    
    	int product;
    	product = Object->Fifo[Object->ReadPos];
    	Object->ReadPos = (Object->ReadPos+1)%FIFO_SIZE;
    	printf("2 done \n");
    	pthread_cond_signal(&(Object->Nfull_Signal));
    }
    
    static void*thread_produce_func(void* arg)
    {
    	Product* Object = &product;
    	while(1)
    	{
    		pthread_mutex_lock(&(Object->locke));
    		printf("1............\n");
    		creat_product(Object);
    		pthread_mutex_unlock(&(Object->locke));
    		sleep(1);
    	}
    	return 0;
    }
    
    
    static void*thread_consume_func(void* arg)
    {
    	Product* Object = &product;
    	while(1)
    	{
    		pthread_mutex_lock(&(Object->locke));
    		printf("2............ \n");
    		consume_product(Object);
    		pthread_mutex_unlock(&(Object->locke));
    		sleep(3);
    	}
    	return 0;
    }
    
    int main(int argc, const char *argv[])
    {
        pthread_t t[2];
    
        Product *Object = &product;
        
        memset(Object,0x00,sizeof(Product));
    
        pthread_mutex_init(&(Object->locke), NULL);
        pthread_cond_init(&(Object->Nfull_Signal), NULL);
        pthread_cond_init(&(Object->Nemp_Signal), NULL);
        
        pthread_create( &t[0], NULL, thread_produce_func, NULL );
        pthread_create( &t[1], NULL, thread_consume_func, NULL );
    
        pthread_join(t[0], NULL);
        pthread_join(t[1], NULL);
        
        return 0;
    }
    

    1............

    1 done 

    2............ 

    2 done 

    1............

    1 done 

    1............

    1 done 

    2............ 

    2 done 

    1............

    1 done 

    1............

    1 done 

    1............

    1 done

     2............ 

    2 done 

    1............

    1 done 

    1............

    1 done 

    1............

    1 done 

    2............ 

    2 done 

    1............

    1 done 

    1............

    1 done 

    1............

    1 done 

    2............ 

    2 done 

    1............

    1 done 

    1............

    1 done 

    1............

    1 wait.......... 

    2............ 

    2 done 

    1 done 

    1............

    1 wait.......... 

    2............

     2 done 

    1 done 

    1............

    1 wait...


    由于消费者线程执行会sleep(3)而生产者sleep(1)所以会出现buffer慢的时候,此时生产者等待消费者消费(1 wait),当消费者消费buffer由满变为不满时有会唤醒生产者线程。。。。。


    展开全文
  • Pthread Primer

    热门讨论 2009-02-05 13:11:37
    Pthread Primer! Pthread线程经典之作!
  • int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, (void*)(*start_rtn)(void*),void *arg); 第一个参数为指向线程标识符的指针。 第二个参数用来设置线程属性。 第三个参数是线程运行...
  • 多线程编程之pthread_create函数应用 pthread_create函数 函数简介 pthread_create是UNIX环境创建线程函数 头文件 #include 函数声明 int pthread_create(pthread_t *thread, pthread_attr_t ...
  • linux中pthread_join()与pthread_detach()详解

    万次阅读 多人点赞 2018-08-04 13:49:05
    1.linux线程执行和windows不同,pthread有两种状态joinable状态和unjoinable状态,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。...
  • 文章目录编程环境:单个线程退出 pthread_exit():阻塞等待退出线程退出 pthread_jion():线程分离 pthread_detach():杀死(取消)线程 pthread_cancel():判断线程是否相等 pthread_equal():下载地址: 简 述: 上一篇...
  • pthread 编程

    2019-01-17 23:42:24
    文章目录pthread api创建线程终止线程线程管理栈管理其它互斥量创建与销毁互斥量锁定与解锁互斥量条件变量创建与销毁条件变量等待与通知所有库函数 pthread api 对 pthread api 进行归类, 可以分为四类: 线程...
  • 1、介绍API 1、pthread_create函数 函数简介 pthread_create是UNIX环境创建线程函数 ...int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *re
  • int pthread_join(pthread_t thread, void **retval); int pthread_detach(pthread_t thread); void pthread_exit(void *retval);   线程正常终止的方法: 1、return从线程函数返回。 2、通过调用函数pthread...
  • 线程,有时被称为轻量进程,在我们平常开发中经常会用到,关于线程的介绍网上有很多,在这里我就不再做介绍了。我主要介绍下载Linux... int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *res...
  • pthread_sigmask

    千次阅读 2019-07-09 03:04:28
    pthread_sigmask 跟 sigprocmask 类似; sigprocmask 只能用于单进程单线程; fork的子进程拥有一份屏蔽信号拷贝; pthread_sigmask 用于多线程 ; 新线程拥有一份pthread_create那个线程的屏蔽信号...
  • linux创建线程pthread_create pthread_join

    千次阅读 2019-03-19 14:18:11
    pthread_create 函数简介: pthread_create是UNIX环境创建线程函数 头文件: #include<pthread.h> 函数声明: int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,...
  • pthread_create和pthread_detach, pthread_cancel的使用
  • pthread_once_t once_control = PTHREAD_ONCE_INIT; int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)); 这个api的主要用途是确保所有的线程传入同一个once_control时,init_routine...
  • pthread_key是线程私有变量,它可以使多个线程用一个全局变量,但各自持有不同的值(通过pthread_set_specific调用)。 pthread_key的使用一般是经过pthread_key_create-&gt;pthread_set_specific-&gt;...
  • 多线程函数系列pthread_create(), pthread_join(), pthread_self(),pthread_exit(), pthread_detach()实例详解
  • [pthread] pthread_join 与 pthread_detach

    千次阅读 2011-05-06 10:38:00
    [pthread] pthread_join 与 pthread_detach
  • pthread_kill和pthread_cancel

    千次阅读 2016-09-20 16:25:53
    pthread_kill和pthread_cancel 为了讲述标题两个函数,必须先介绍一下pthread线程库。 pthread线程库是遵循POSIX标准开发的,工作非常稳定,性能也不差, 所以在Linux下是最常用的线程库。但从调用上看,pthread...
  • pthreadpthread_join()函数理解实验

    万次阅读 多人点赞 2015-01-20 18:51:55
    pthread_t tid; pthread_create(&tid, NULL, thread_run,NULL); pthread_join(tid,NULL); 创建线程之后直接调用pthread_join方法就行了。 二、为什么要使用pthread_join()   在很多情况下,主线程生成并起...
  • int pthread_mutex_init (pthread_mutex_t *__mutex,const pthread_mutexattr_t *__mutexattr)创建一个锁; int pthread_mutex_destroy (pthread_mutex_t *__mutex)销毁锁; int pthread_mutex_trylock (pthread_mute....
  • pthread_create()函数 创建线程 A:依赖的头文件 #include B:函数声明 int pthread_create(pthread_t *thread, constpthread_attr_t *attr, void *(*start_routine) (void *), void *arg); pthread_t
  • 最近阅读一份linux的线程代码时,看到了一套函数,pthread_getspecific和pthread_setspecific函。光从名字上,完全无法理解出他们到底是干啥的,结合代码来看,也不是很清楚。于是就去百度。可是,百度来百度去,...
  • pthread线程库函数

    千次阅读 2019-05-31 18:43:14
    所有线程都有一个线程号,也就是Thread ID。其类型为pthread_t。通过调用pthread_self()函数...int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void...
  • pthread_kill 与pthread_cancel使用方法#include <pthread.h> #include #include #include <unistd.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INIT

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 122,059
精华内容 48,823
关键字:

Pthread