pthread相关资料整理(1)

来源:互联网 发布:学象棋的软件 编辑:程序博客网 时间:2024/04/30 13:24

1.   pthread线程概念

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。与vxworks上任务的概念类似,都是调度的最小单元,都有共享的堆、栈、代码区、全局变量等。

 

2.   创建线程

int  pthread_create(pthread_t  *  thread,

pthread_attr_t * attr,

void * (*start_routine)(void *),

void * arg)

thread:返回创建的线程的ID

attr:线程属性,调度策略、优先级等都在这里设置,如果为NULL则表示用默认属性

start_routine:线程入口函数,可以返回一个void*类型的返回值,该返回值可由pthread_join()捕获

arg:传给start_routine的参数,可以为NULL

返回值:成功返回0

 

3.   设置线程属性

线程属性通过attr进行设置。

设置与查询attr结构的为pthread_attr_get***()pthread_attr_set***()两个函数系列。

设置与查询线程参数的为pthread_get***()pthread_set***()两个函数系列。也可以在创建时通过pthrea_create传入参数。

注:有些必须在线程创建时设置,如调度策略。

 

3.1.  调度策略

调度策略有三种:

SCHED_OTHER:非实时、正常

SCHED_RR:实时、轮询法

SCHED_FIFO:实时、先入先出,与vxworks的调度机制一致

例程:

pthread_attr_t attr;

pthread_attr_init(&attr);

pthread_attr_setschedpolicy(&attr, SCHED_FIFO);//sched_policy

 

3.2.  优先级

线程优先级支持两种设置方式,一种是创建时设置,另一种是创建后动态设置

例程:

pthread_attr_setschedparam(&attr, new_priority);

如果是静态设置,则之后会用该属性创建任务,如果是动态设置,则使用下列函数设置:

pthread_attr_setschedparam(&attr, &task->prv_priority);

pthread_attr_getschedparam(&attr, &schedparam);

schedparam.sched_priority = new_priority;

pthread_attr_setschedparam(&attr, &schedparam);

pthread_setschedparam(pthrid, sched_policy, &schedparam);

 

3.3.  脱离同步

Pthread_join()函数可以使主线程与子线程保持同步。如果设置了detachstate状态,则pthread_join()会失效,线程会自动释放所占用的资源。线程的缺省状态为PHREAD_CREATE_JOINABLE状态,线程运行起来后,一旦被设置为PTHREAD_CREATE_DETACH状态,则无法再恢复到joinable状态。

例程:

pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED);、

 

3.4.  调度继承

线程A创建了线程B,则线程B的调度策略是与线程A的调度策略与线程B的继承策略有关的。如果线程B继承策略为PTHREAD_INHERIT_SCHED,则线程B的调度策略与线程A相同;如果线程B继承策略为PTHREAD_EXPLICIT_SCHE,则线程B的调度策略由attr决定。

pthread_attr_setdetachstate (&attr,PTHREAD_CREATE_DETACHED);

4.   线程取消

4.1.  线程取消定义

Pthread线程可以通过发送取消请求的方式终止一个线程的运行。

取消线程的操作主要应用于下列场景中:有一个线程在使用select监控网口,主控线程此时接到了用户的通知,要放弃监听,则主控线程会向监听线程发送取消请求。

Linuxpthread线程接收到取消请求时,并不会立刻终止线程,而是要等到取消点时才会结束任务。这样我们可以为取消点建立某些特殊的处理。Select是取消点,所以可以退出。

 

4.2.  取消点

Vxworks可以在任意位置杀死任务,这样做导致任务被杀死后的位置不可控,所以vxworks中不会很少使用taskDeleteHook

Pthread规定了取消点的概念。不论线程何时收到取消请求,都只能在取消点上才能取消线程。这就保证了风险的可控。

Pthread标准指定了以下几个取消点:

Ø  pthread_testcancel

Ø  所有调度点,如pthread_cond_waitsigwaitselectsleep

根据POSIX标准,read()write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:

pthread_testcancel();

retcode = read(fd, buffer, length);

pthread_testcancel();

这段代码可以保证read附近有取消点,但是否有可能会卡在read中无法返回呢?

 

如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。

 

4.3.  线程取消函数

int pthread_cancel(pthread_t thread)

发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。

int pthread_setcancelstate(int state, int *oldstate)

设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复。

int pthread_setcanceltype(int type, int *oldtype)

设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入原来的取消动作类型值。

void pthread_testcancel(void)

检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。

 

4.4.  例程

#include

#include

#include

 

pthread_key_t key;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t cond =   PTHREAD_COND_INITIALIZER;

unsigned long abc=0;

 

void* Test03(void *p)

{

    printf("Cancel point");

    return NULL;

}

 

void* Test01(void* ptr)

{

    pthread_cleanup_push(Test03, NULL); /* push */

    while(1)

    {

        abc++;

        pthread_testcancel();

    }

    pthread_cleanup_pop(0); /* pop */

    return NULL;

}

 

void* Test02(void* ptr)

{

    while(1)

    {

        sleep(2);

        printf("2222cond_wait:abc=0x%08x\n", abc);

    }

   

    return NULL;

}

 

int main(void)

{

    int tid1, tid2;

    int ret;

 

    printf("Start:\n");

    ret = pthread_create(&tid1, NULL, Test01, NULL);

    ret = pthread_create(&tid2, NULL, Test02, NULL);

 

    sleep(6);

    pthread_cancel(tid1);

    pthread_join(tid1, NULL);

    pthread_join(tid2, NULL);

   

    return 0;

}

 

结果:

Start:

2222cond_wait:abc=0x22c29a05

2222cond_wait:abc=0x47b49007

Cancel point2222cond_wait:abc=0x6c9de3ad

2222cond_wait:abc=0x6c9de3ad

2222cond_wait:abc=0x6c9de3ad

 

如果不加取消点pthread_testcancel(),线程1无法退出。

5.   线程终止方式

线程终止有两种情况(不考虑进程),一种是线程主体函数return时,线程会自动终止,此时的退出是可预知的;另一种是其它线程向通以进程下的线程发送取消请求,线程会根据情况判断是否终止,此时的终止是不可预知的。

Vxworks中的任务与此类似。任务的主体函数return时,任务会自动终止;其它任务调用taskDelete()可以杀死任意一个任务。TaskDelete()并不安全,因为任务可能被杀死在任意一个时刻。

5.1.  线程终止时的清理

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

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

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为执行;这个参数并不影响异常终止时清理函数的执行。

 

这两个其实是宏,必须成对出现,否则会编译不过。

编译。在下面的例子里,当线程在"do some 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模式。为此,pthread中还提供了一对不保证可移植的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);

 }

 

5.2.  线程终止的同步及其返回值

一般情况下,进程中各个线程的运行都是相互独立的,线程的终止并不会通知,也不会影响其他线程,终止的线程所占用的资源也并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。

void pthread_exit(void *retval)

int pthread_join(pthread_t th, void **thread_return)

int pthread_detach(pthread_t th)

 

pthread_join()的调用者将挂起并等待th线程终止,retvalpthread_exit()调用者线程(线程IDth)的返回值,如果thread_return不为NULL,则*thread_return=retval。需要注意的是一个线程仅允许唯一的一个线程使用pthread_join()等待它的终止,并且被等待的线程应该处于可join状态,即非DETACHED状态。

如果进程中的某个线程执行了pthread_detach(th),则th线程将处于DETACHED状态,这使得th线程在结束运行时自行释放所占用的内存资源,同时也无法由pthread_join()同步,pthread_detach()执行之后,对th请求pthread_join()将返回错误。

一个可join的线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放,因此为了避免内存泄漏,所有线程的终止,要么已设为DETACHED,要么就需要使用pthread_join()来回收。

 

5.3.  关于ptread_exite()return

理论上说,pthread_exit()和线程宿体函数退出的功能是相同的,函数结束时会在内部自动调用pthread_exit()来清理线程相关的资源。但实际上二者由于编译器的处理有很大的不同。

在进程主函数(main())中调用pthread_exit(),只会使主函数所在的线程(可以说是进程的主线程)退出;而如果是return,编译器将使其调用进程退出的代码(如_exit()),从而导致进程及其所有线程结束运行。

其次,在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault

 

5.4.  判断是否为同一个线程

int pthread_equal(pthread_t thread1, pthread_t thread2)

判断两个线程描述符是否指向同一线程。在LinuxThreads中,线程ID相同的线程必然是同一个线程,因此,这个函数的实现仅仅判断thread1thread2是否相等。

 

5.5.  仅执行一次的操作

int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))

本函数使用初值为PTHREAD_ONCE_INITonce_control变量保证init_routine()函数在本进程执行序列中仅执行一次。这个类似与线程的构造函数。

#include

#include

pthread_once_t  once=PTHREAD_ONCE_INIT;

void    once_run(void)

{

        printf("once_run in thread %d\n",pthread_self());

}

void * child1(void *arg)

{

        int tid=pthread_self();

        printf("thread %d enter\n",tid);

        pthread_once(&once,once_run);

        printf("thread %d returns\n",tid);

}

void * child2(void *arg)

{

        int tid=pthread_self();

        printf("thread %d enter\n",tid);

        pthread_once(&once,once_run);

        printf("thread %d returns\n",tid);

}

int main(void)

{

        int tid1,tid2;

        printf("hello\n");

        pthread_create(&tid1,NULL,child1,NULL);

        pthread_create(&tid2,NULL,child2,NULL);

        sleep(10);

        printf("main thread exit\n");

        return 0;

}

 

once_run()函数仅执行一次,且究竟在哪个线程中执行是不定的,尽管pthread_once(&once,once_run)出现在两个线程中。

LinuxThreads使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次,而once_control则表征是否执行过。如果once_control的初值不是PTHREAD_ONCE_INITLinuxThreads定义为0),pthread_once()的行为就会不正常。在LinuxThreads中,实际"一次性函数"的执行状态有三种:NEVER0)、IN_PROGRESS1)、DONE2),如果once初值设为1,则由于所有pthread_once()都必须等待其中一个激发"已执行一次"信号,因此所有pthread_once()都会陷入永久的等待中;如果设为2,则表示该函数已执行过一次,从而所有pthread_once()都会立即返回0

pthread_kill_other_threads_np()

void pthread_kill_other_threads_np(void)

这个函数是LinuxThreads针对本身无法实现的POSIX约定而做的扩展。POSIX要求当进程的某一个线程执行exec*系统调用在进程空间中加载另一个程序时,当前进程的所有线程都应终止。由于LinuxThreads的局限性,该机制无法在exec中实现,因此要求线程执行exec前手工终止其他所有线程。pthread_kill_other_threads_np()的作用就是这个。

需要注意的是,pthread_kill_other_threads_np()并没有通过pthread_cancel()来终止线程,而是直接向管理线程发"进程退出"信号,使所有其他线程都结束运行,而不经过Cancel动作,当然也不会执行退出回调函数。尽管LinuxThreads的实验结果与文档说明相同,但代码实现中却是用的__pthread_sig_cancel信号来kill线程,应该效果与执行pthread_cancel()是一样的,其中原因目前还不清楚。

6.   TSD

6.1.  TSD概念

在单线程程序中,我们经常要用到"全局变量"以实现多个函数间共享数据。在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问,比如程序可能需要每个线程维护一个链表,而使用相同的函数操作,最简单的办法就是使用同名而不同变量地址的线程相关数据结构。这样的数据结构可以由Posix线程库维护,称为线程私有数据(Thread-specific Data,或TSD)。

 

6.2.  创建与注销

Posix定义了两个API分别用来创建和注销TSD:

int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *)) 该函数从TSD池中分配一项,将其值赋给key供以后访问使用。如果destr_function不为空,在线程退出(pthread_exit())时将以key所关联的数据为参数调用destr_function(),以释放分配的缓冲区。不论哪个线程调用pthread_key_create(),所创建的key都是所有线程可访问的,但各个线程可根据自己的需要往key中填入不同的值,这就相当于提供了一个同名而不同值的全局变量。在LinuxThreads的实现中,TSD池用一个结构数组表示:static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };创建一个TSD就相当于将结构数组中的某一项设置为"in_use",并将其索引返回给*key,然后设置destructor函数为destr_function。注销一个TSD采用如下API:int pthread_key_delete(pthread_key_t key)这个函数并不检查当前是否有线程正使用该TSD,也不会调用清理函数(destr_function),而只是将TSD释放以供下一次调用pthread_key_create()使用。在LinuxThreads中,它还会将与之相关的线程数据项设为NULL(见"访问")。6.3.  访问TSD的读写都通过专门的Posix Thread函数进行,其API定义如下:int  pthread_setspecific(pthread_key_t  key,  const   void  *pointer)void * pthread_getspecific(pthread_key_t key)写入(pthread_setspecific())时,将pointer的值(不是所指的内容)与key相关联,而相应的读出函数则将与key相关联的数据读出来。数据类型都设为void *,因此可以指向任何类型的数据。在LinuxThreads中,使用了一个位于线程描述结构(_pthread_descr_struct)中的二维void *指针数组来存放与key关联的数据,数组大小由以下几个宏来说明:#define PTHREAD_KEY_2NDLEVEL_SIZE       32#define PTHREAD_KEY_1STLEVEL_SIZE   \((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1)/ PTHREAD_KEY_2NDLEVEL_SIZE)    其中在/usr/include/bits/local_lim.h中定义了PTHREAD_KEYS_MAX为1024,    因此一维数组大小为32。而具体存放的位置由key值经过以下计算得到:idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZEidx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE

也就是说,数据存放与一个32×32的稀疏矩阵中。同样,访问的时候也由key值经过类似计算得到数据所在位置索引,再取出其中内容返回。

 

6.4.  使用范例

/* fireaxe的例程 */

#include

#include

pthread_key_t   key;

void echomsg(int t)

{

        printf("destructor excuted in thread %d,param=%d\n",pthread_self(),t);

}

void * child1(void *arg)

{

        int tid=pthread_self();

        printf("thread %d enter\n",tid);

        pthread_setspecific(key,(void *)tid);

        sleep(2);

        printf("thread %d returns %d\n",tid,pthread_getspecific(key));

        sleep(5);

}

void * child2(void *arg)

{

        int tid=pthread_self();

        printf("thread %d enter\n",tid);

        pthread_setspecific(key,(void *)tid);

        sleep(1);

        printf("thread %d returns %d\n",tid,pthread_getspecific(key));

        sleep(5);

}

int main(void)

{

        int tid1,tid2;

        printf("hello\n");

        pthread_key_create(&key,echomsg);

        pthread_create(&tid1,NULL,child1,NULL);

        pthread_create(&tid2,NULL,child2,NULL);

        sleep(10);

        pthread_key_delete(key);

        printf("main thread exit\n");

        return 0;

}

 

给例程创建两个线程分别设置同一个线程私有数据为自己的线程ID,为了检验其私有性,程序错开了两个线程私有数据的写入和读出的时间,从程序运行结果可以看出,两个线程对TSD的修改互不干扰。同时,当线程退出时,清理函数会自动执行,参数为tid

7.   线程同步

7.1.  互斥锁(互斥信号量)

Pthread的互斥锁与vxworks的互斥信号量类似,都是用于互斥保护。

需要注意的是,linux有取消点的概念,即任务在运行时可以被取消。如果使用了互斥锁,可能会造成为解锁就被取消。为了解决这一问题,linux提供了可以在取消上加回调函数的功能:

pthread_cleanup_push()

pthread_cleanup_pop()

两个必须成对出现如果线程运行到两个函数之间被取消时,push注册的函数会被调用。

例程:

如果没有pushpop两行,则tid1被杀死后会导致tid2无法获取到互斥锁。

/* fireaxe的例程 */

#include

#include

#include

 

pthread_key_t key;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t cond =   PTHREAD_COND_INITIALIZER;

 

void* Test01(void* ptr)

{

    pthread_cleanup_push(pthread_mutex_unlock, &mutex); /* push */

    while(1)

    {

        sleep(3);

        pthread_mutex_lock(&mutex);

        printf("1111mutex_lock\n");

        pthread_cond_wait(&cond, &mutex);

        printf("1111cond_wait\n");

        pthread_mutex_unlock(&mutex);

    }

    pthread_cleanup_pop(0); /* pop */

    return NULL;

}

 

void* Test02(void* ptr)

{

    while(1)

    {

        sleep(2);

        pthread_mutex_lock(&mutex);

        printf("2222mutex_lock\n");

        pthread_cond_wait(&cond, &mutex);

        printf("2222cond_wait\n");

        pthread_mutex_unlock(&mutex);

        sleep(2);

    }

   

    return NULL;

}

 

int main(void)

{

    int tid1, tid2;

    int ret;

 

    pthread_mutex_init(&mutex, NULL);

    pthread_mutex_init(&mutex, NULL);

    printf("Start:\n");

    ret = pthread_create(&tid1, NULL, Test01, NULL);

    ret = pthread_create(&tid2, NULL, Test02, NULL);

    printf("ret = 0x%x\n", ret);

 

    sleep(4);

    pthread_cancel(tid1);

   

    do

    {

        sleep(4);

        pthread_cond_signal(&cond);

    } while(1);

    printf("end.\n");

   

    return 0;

   

}

 

7.2.  条件变量(同步信号量)

Pthread的条件变量与vxworks中同步信号量类似,都是用于任务同步。区别是条件变量的操作不是原子操作,需要借助互斥锁保证其原子性。7.3.  信号灯(计数信号量)

Pthread的信号灯与vxworks中计数信号量信号量类似,都是用于表示资源是否可用。

 

 

 

Outline 

- 1.线程特点

- 2.pthread创建

- 3.pthread终止

        - 4.mutex互斥量使用框架

        - 5.cond条件变量

        - 6.综合实例

================================================================================================

1. 线程特点

线程拥有自己独立的栈、调度优先级和策略、信号屏蔽字(创建时继承)、errno变量以及线程私有数据。进程的其他地址空间均被所有线程所共享,因此线程可以访问程序的全局变量和堆中分配的数据,并通过同步机制保证对数据访问的一致性。

2. pthread创建
pthread有一个线程ID,类型为pthread_t,在使用printf打印时,应转换为u类型。
pthread_equal可用于比较两个id是否相等;pthread_self用于获取当前线程的ID。
pthread_create用于创建新的线程,可以给线程传入一个void *类型的参数,例如一个结构体指针或者一个数值。
系统并不能保证哪个线程会现运行:新创建的线程还是调用线程。

3. pthread终止
a) 从线程函数中返回
b) 被同一进程中的其他线程取消
c) 线程调用pthread_exit
注意,线程的返回值需要转换为void *类型。

pthread_exit(void *ret)
pthread_join(pthread_t id, void **ret)

ret均可设置为NULL

4.  mutex 互斥量使用框架
pthread_mutex_t lock;
pthread_mutex_init 或者 PTHREAD_MUTEX_INITIALIZER(仅可用在静态变量)
pthread_mutex_lock / pthread_mutex_unlock / pthread_mutex_trylock
pthread_mutex_destroy

5. cond 条件变量
pthread_cond_t qready;
pthread_mutex_t qlock;
pthread_mutex_init 或者 PTHREAD_MUTEX_INITIALIZER
pthread_cond_init 或者 PTHREAD_COND_INITIALIZER
pthread_mutex_lock(&qlock...)
pthread_cond_wait(&qready, &qlock...) / pthread_cond_timewait
pthread_mutex_unlock(&qlock)
pthread_cond_destroy

//唤醒条件变量
pthread_cond_signal
pthread_cond_broadcast

条件变量是pthread中比较难以理解的一点,主要会产生以下疑惑:
Q1. 假如在调用pthread_{cond_wait | cond_timedwait}之前就调用pthread_cond_{signal | broadcast}会发生什么?

Q2. pthread_cond_{cond_wait | cond_timewait}为什么需要一个已经锁住的mutex作为变量?

Q3. pthread_cond_{signal | broadcast}使用之前必须获取wait中对应的mutex吗?

Q4. 假如pthread_cond_{signal | broadcast}必须获取mutex,那么下列两种形式,哪种正确?为什么?

 1)
 lock(lock_for_X);
 change(X);
 unlock(lock_for_X);
 pthread_cond_{signal | broadcast};

 2)
 lock(lock_for_X);
 change(X);
 pthread_cond_{signal | broadcast};
 unlock(lock_for_X);

----思考-------思考-------思考-------思考------思考-------思考------思考------思考-------思考-------

A1: 什么都不会发生,也不会出错,仅仅造成这次发送的signal丢失。

A2: 一般场景如下,我们需要检查某个条件是否满足(如队列X是否为空、布尔Y是否为真),假如没有条件变量,我们唯一的选择是

  1. while (1) {
  2.   lock(lock_for_X);

  3.   if (Xis not empty) {
  4.     unlock(lock_for_X);
  5.     break;
  6.   } else{ //Xis empty,loop continues
  7.     unlock(lock_for_X);
  8.     sleep(10);
  9.   }
  10. }
  11. //X is not empty,loop ends
  12. process(X);

明显这种轮询的方式非常耗费CPU时间,这时候我们很容易的想到,如果有一种机制,可以异步通知我们队列的状态发生了变化,那么我们便无须再轮询,只要等到通知到来时再检查条件是否满足即可,其他时间则将程序休眠,因此现在代码变成这样:

  1. while (1){
  2.   lock(lock_for_X);
  3.   if (Xis not empty) {
  4.     unlock(lock_for_X);
  5.     break;
  6.   } else{
  7.     unlock(lock_for_X);//must called before my_wait(), otherwise no one can acquire the lockand make change to X
  8.     -------------------------------------->窗口,由于已经解锁,其他程序可能改变X,并且试图唤醒mywait,但在一个繁忙的系统中,可能此时my_还没被调用!
  9.     my_wait();//go to sleep and wait for the notification
  10.   }
  11. }

my_wait是一个假想的函数,作用如注释所示。
不难发现,这样做以后,我们无须再轮询了,只需要等待my_wait()被唤醒以后检查条件是否满足。
但是请注意,正如图中所示,存在1个时间窗口。若其他程序在这个窗口中试图唤醒my_wait,由于此时my_wait还没有被调用,那么这个信号将丢失,造成my_wait一直阻塞。解决的办法就是,要将unlock和my_wait合并成一个原子操作,这样就不会被其他程序插入执行。我想到这里,你应该已经明白了,这个原子操作的函数就是pthread_cond_{signal | broadcast}.

A3: 是的。
详见:http://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex

A4: 对于1),在不同的操作系统中,可能会造成不确定的调度结果(可能会造成调度优先级反转);对于2)可以保证无论在何种操作系统中都将获得预期的调度顺序。

设想一个场景:有两个消费者线程A和B,我们设定A的优先级比B高,A正在等待条件变量被出发,即已经调用了pthread_wait,并且处于阻塞状态:

  1. lock(lock_for_X);
  2. while (Xis empty){
  3.   pthread_cond_wait(&qready,&lock_for_X);
  4. }
  5. unlock(lock_for_X);

B中没有调用pthread_wait,而是做类似如下的处理:

  1. while(1){
  2.   lock(lock_for_X);
  3.   dequeue(X);
  4.   unlock(lock_for_X);
  5. }
另一个线程C,为生产者,采用1)方案,则代码如下,先unlock,再发出signal:

 lock(lock_for_X);
 change(X);
 unlock(lock_for_X);
 pthread_cond_{signal | broadcast};

当发出unlock以后,发送signal之前,此时消费者B已经满足了运行条件,而消费者A虽然优先级比B高,但是由于其运行条件还需要signal,所以不具备立刻运行的条件,此时就看操作系统如何实现调度算法了。有些操作系统,可能会因为A不具备立刻运行条件,即使它的优先级比B高,此时还是让B线程先运行,那么,后续将分成两种情况:

(a) B获得了lock,但是还没有将X队列中的刚刚加入的条目移除,此时C调用了signal,A接收到了signal,由于A的优先级高,那么A抢占B,A从函数pthread_cond_wait返回之前需要再次将lock上锁,但是A抢占后发现,lock被人锁住了(还没有被B释放),只好再次休眠,等待锁被释放,结果B又被唤醒,也可能因此造成A和B的死锁,这个具体要看操作系统的调度算法。

(b) B获得了lock,并且执行了dequeue,然后释放了锁。此时C调用了signal,A接收到了signal,由于A的优先级高,那么A抢占B,A这次顺利的获取了锁得以从pthread_cond_wait中返回,但是在检查条件时,却发现队列是空的,于是乎再次进入pthread_cond_wait休眠。结果A又无法被执行,A可能由此进入饥饿状态。

但是如果C采用2)方案:

 lock(lock_for_X);
 change(X);
 pthread_cond_{signal | broadcast};
 unlock(lock_for_X);

在unlock以后,A、B都具备了立即运行的条件,由于A比B的优先级高,因此操作系统必定会先调度A执行,就避免了前面一种不确定的调度结果。

主要参考:http://groups.google.com/group/comp.programming.threads/msg/a3721a2fc9b21c64?hl=ky

6. 综合实例
  1. /*
  2.  * =====================================================================================
  3.  *
  4.  * Filename: pthread.c
  5.  *
  6.  * Description:
  7.  *
  8.  * Version: 1.0
  9.  * Created: 08/17/11 11:06:35
  10.  * Revision: none
  11.  * Compiler: gcc
  12.  *
  13.  * Author: YOUR NAME(),
  14.  * Company:
  15.  *
  16.  * =====================================================================================
  17.  */
  18. #include <stdio.h>
  19. #include <pthread.h>
  20. #include <error.h>
  21. #include <stdlib.h>
  22. #include <unistd.h>
  23. #include <string.h>

  24. pthread_cond_t qready;
  25. pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;

  26. struct foo {
  27.     int cnt;
  28.     pthread_mutex_t f_lock;
  29. };

  30. void cleanup(void *arg)
  31. {
  32.     printf("clean up: %s\n",(char *)arg);
  33. }

  34. void printids(char *str)
  35. {
  36.     printf("%s pid = %u tid = %u / 0x%x\n",
  37.             str, (unsigned int)getpid(),(unsigned int)pthread_self(),(unsigned int)pthread_self());
  38. }

  39. void *thread1(void*arg)
  40. {
  41.     pthread_mutex_lock(&qlock);
  42.     pthread_cond_wait(&qready,&qlock);
  43.     pthread_mutex_unlock(&qlock);

  44.     printids("thread1:");
  45.     
  46.     pthread_cleanup_push(cleanup,"thread 1 first cleanup handler");
  47.     pthread_cleanup_push(cleanup,"thread 1 second cleanup handler");
  48.     printf("thread 1 push complete!\n");
  49.     
  50.     pthread_mutex_lock(&((struct foo*)arg)->f_lock);
  51.     ((struct foo*)arg)->cnt;
  52.     printf("thread1: cnt = %d\n",((struct foo*)arg)->cnt);
  53.     pthread_mutex_unlock(&((struct foo*)arg)->f_lock);

  54.     if (arg)
  55.         return ((void*)0);
  56.     
  57.     pthread_cleanup_pop(0);
  58.     pthread_cleanup_pop(0);

  59.     pthread_exit((void*)1);
  60. }

  61. void *thread2(void*arg)
  62. {
  63.     int exit_code = -1;
  64.     printids("thread2:");
  65.     
  66.     printf("Now unlock thread1\n");

  67.     pthread_mutex_lock(&qlock);
  68.     pthread_mutex_unlock(&qlock);
  69.     pthread_cond_signal(&qready);

  70.     printf("Thread1 unlocked\n");

  71.     pthread_cleanup_push(cleanup,"thread 2 first cleanup handler");
  72.     pthread_cleanup_push(cleanup,"thread 2 second cleanup handler");
  73.     printf("thread 2 push complete!\n");
  74.     
  75.     if (arg)
  76.         pthread_exit((void*)exit_code);

  77.     pthread_cleanup_pop(0);
  78.     pthread_cleanup_pop(0);
  79.     
  80.     pthread_exit((void*)exit_code);
  81. }

  82. int main(int argc, char*argv[])
  83. {
  84.     int ret;
  85.     pthread_t tid1, tid2;
  86.     void *retval;
  87.     struct foo *fp;

  88.     ret = pthread_cond_init(&qready,NULL);
  89.     if (ret!= 0){
  90.         printf("pthread_cond_init error: %s\n", strerror(ret));
  91.         return -1;
  92.     }



  93.     if ((fp= malloc(sizeof(struct foo)))== NULL) {
  94.         printf("malloc failed!\n");
  95.         return -1;
  96.     }

  97.     if (pthread_mutex_init(&fp->f_lock,NULL) != 0){
  98.         free(fp);
  99.         printf("init mutex failed!\n");
  100.     }

  101.     pthread_mutex_lock(&fp->f_lock);

  102.     ret = pthread_create(&tid1,NULL, thread1,(void *)fp);
  103.     if (ret!= 0){
  104.         printf("main thread error: %s\n", strerror(ret));
  105.         return -1;
  106.     }
  107.     ret = pthread_create(&tid2,NULL, thread2,(void *)1);
  108.     if (ret!= 0){
  109.         printf("main thread error: %s\n", strerror(ret));
  110.         return -1;
  111.     }

  112.     
  113.     ret = pthread_join(tid2,&retval);
  114.     if (ret!= 0){
  115.         printf("pthread join falied!\n");
  116.         return -1;
  117.     }
  118.     else
  119.         printf("thread2 exit code %d\n",(int)retval);

  120.     fp->cnt= 1;
  121.     printf("main thread: cnt = %d\n",fp->cnt);

  122.     pthread_mutex_unlock(&fp->f_lock);

  123.     sleep(1);    //thereis no guarantee the main thread will run before the newly created thread, so we waitfor a while
  124.     printids("main thread:");

  125.     printf("Press to exit\n");

  126.     ret = pthread_cond_destroy(&qready);
  127.     if (ret!= 0){
  128.         printf("pthread_cond_destroy error: %s\n", strerror(ret));
  129.         return -1;
  130.     }

  131.     getchar();
  132.     return 0;
  133. }

 

1.    线程属性:
            
使用pthread_attr_t类型表示,我们需要对此结构体进行初始化,
 
                初始化后使用,使用后还要进行去除初始化!
                 pthread_attr_init:初始化
                 pthread_attr_destory:去除初始化       
               
                #include
                int pthread_attr_init(pthread_attr_t *attr);
                int pthread_attr_destroy(pthread_attr_t *attr);   
                若成功返回0,若失败返回-1。
               
                pthread_attr_init之后,pthread_t结构所包含的内容就是操作系统实现
                支持的线程所有属性的默认值。
               
                如果pthread_attr_init实现时为属性对象分配了动态内存空间,
                pthread_attr_destroy还会用无效的值初始化属性对象,因此如果经
                pthread_attr_destroy去除初始化之后的pthread_attr_t结构被
                pthread_create函数调用,将会导致其返回错误。
               
                线程属性结构如下:

                typedef struct
                {
                       int                           detachstate;     线程的分离状态
                       int                          schedpolicy;   线程调度策略
                       struct sched_param      schedparam;   线程的调度参数
                       int                          inheritsched;    线程的继承性
                       int                          scope;          线程的作用域
                       size_t                      guardsize; 线程栈末尾的警戒缓冲区大小
                       int                          stackaddr_set;
                       void *                     stackaddr;      线程栈的位置
                       size_t                      stacksize;       线程栈的大小
                }pthread_attr_t;
               
                下面主要讨论此结构体!!!
               
2.    分离状态:
            线程的分离状态决定一个线程以什么样的方式来终止自己。
           
            我们已经在前面已经知道,在默认情况下线程是非分离状态的,这种情况   
            下,原有的线程等待创建的线程结束。只有当pthread_join() 函数返回       
            时,创建的线程才算终止,才能释放自己占用的系统资源。   
           
            分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,
            马上释放系统资源。
           
            通俗的说也就是:我们知道一般我们要等待(pthread_join)一个线程的结束,
            主要是想知道它的结束状态,否则等待一般是没有什么意义的!但是if有一
            些线程的终止态我们压根就不想知道,那么就可以使用“分离”属性,那么我
            们就无须等待管理,只要线程自己结束了,自己释放src就可以咯!这样更
            方便!
           
            #include
            int pthread_attr_getdetachstate(const pthread_attr_t * attr, int * detachstate);
            int pthread_attr_setdetachstate(pthread_attr_t * attr, int detachstate);
            参数:attr:线程属性变量
                    detachstate:分离状态属性   
            若成功返回0,若失败返回-1。
           
            设置的时候可以有两种选择:
            <1>.detachstate参数为:PTHREAD_CREATE_DETACHED     分离状态启动
            <2>.detachstate参数为:PTHREAD_CREATE_JOINABLE    正常启动线程
           
3.    线程的继承性:
           
            函数pthread_attr_setinheritsched和pthread_attr_getinheritsched分别用来设
            置和得到线程的继承性!
           
            #include
            int pthread_attr_getinheritsched(const pthread_attr_t *attr,int *inheritsched);
            int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);
            参数:
            attr                线程属性变量
            inheritsched     线程的继承性
            若成功返回0,若失败返回-1。
           
            请注意:
            继承性决定调度的参数是从创建的进程中继承还是使用在 
            schedpolicy和schedparam属性中显式设置的调度信息。           
                                   
            线程没有默认的继承值设置,所以如果关心线程的调度策略和参数,
            只能手动设置!
           
            可设置参数:
            PTHREAD_INHERIT_SCHED: 新的线程继承创建线程的策略和参数!
            PTHREAD_EXPLICIT_SCHED:新的线程继承策略和参数来自于
                                                schedpolicy和schedparam属性中显式
                                                设置的调度信息!
                                               
>>>>>:    下面补充线程调度策略和调度参数:
            <1>.调度策略:
           
                    函数pthread_attr_setschedpolicy和pthread_attr_getschedpolicy分别用
                    来设置和得到线程的调度策略。
                   
                    int pthread_attr_getschedpolicy(const pthread_attr_t *, int * policy)
                    int pthread_attr_setschedpolicy(pthread_attr_*, int policy)
                    参数:
                            attr            线程属性变量
                            policy        调度策略   
                    若成功返回0,若失败返回-1。
                   
                    所谓调度策略也就是我们之前在OS中所学过的那些调度算法:
                    SCHED_FIFO    :先进先出
                    SCHED_RR       :轮转法
                    SCHED_OTHER    :其他方法
                   
                    SCHED_OTHER是不支持优先级使用的,而SCHED_FIFO和SCHED_RR
                    支持优先级的使用,他们分别为1和99,数值越大优先级越高.
                   
                    注意:
                            > 此处的SCHED_FIFO是允许被高优先级抢占的!
                            > 也就是有高优先级的必须先运行
                            > SCHED_RR是设置一个时间片
                            > 当有SCHED_FIFO或SCHED_RR策赂的线程在一个条件变量
                            上等持或等持加锁同一个互斥量时,它们将以优先级顺序被唤
                            醒。即,如果一个低优先级的SCHED_FIFO线程和一个高优先
                            织的SCHED_FIFO线程都在等待锁相同的互斥且,则当互斥量
                            被解锁时,高优先级线程将总是被首先解除阻塞。
                           
            <2>.调度参数:
                   
                    函数pthread_attr_getschedparam 和pthread_attr_setschedparam分别
                    用来设置和得到线程的调度参数。
                   
                       

                    int pthread_attr_getschedparam(const pthread_attr_t *,struct
                    sched_param *);
                    int pthread_attr_setschedparam(pthread_attr_t *,const struct
                    sched_param *);
                    参数:
                            attr            线程变量属性
                            param        sched_parm 结构体
                    若成功返回0,若失败返回-1。
                   
                    /usr/include /bits/sched.h
                    struct sched_param
                    {
                           int sched_priority;    //!> 参数的本质就是优先级
                    };
                    注意:大的权值对应高的优先级!
                    系统支持的最大和最小的优先级值可以用函数:
                    sched_get_priority_max和sched_get_priority_min得到!
                   
                    #include
                    int sched_get_priority_max( int policy );
                    int sched_get_priority_min( int policy );
                    参数:max_:    系统支持的优先级的最小值
                            min_ :    系统支持的优先级的最大值
                   
                    使用:max_ = sched_get_priority_max( policy );
                            min_ = sched_get_priority_min( policy );
                            注意参数是policy调用策略,也就是说对于不同的策略的值是不
                            一样的!
               
                    附录:来自
                    http://www.yuanma.org/data/2006/0823/article_1392.htm
                    policy = SCHED_OTHER
                    max_priority = 0
                    min_priority = 0
   
                    Show SCHED_FIFO of priority
                    max_priority = 99
                    min_priority = 1
                   
                    Show SCHED_RR of priority
                    max_priority = 99
                    min_priority = 1
   
                    Show priority of current thread
                    priority = 0
                   
3.    线程的作用域:
                               
            函数pthread_attr_setscope和pthread_attr_getscope分别
            用来设置和得到线程的作用域。       
            #include    
            int    pthread_attr_getscope( const pthread_attr_t * attr, int * scope );
            int pthread_attr_setscope( pthread_attr_t*, int scope );
            参数:
                    attr               线程属性变量
                    scope         线程的作用域       
            若成功返回0,若失败返回-1。
           
            作用域控制线程是否在进程内或在系统级上竞争资源,可能的值是
            PTHREAD_SCOPE_PROCESS(进程内竞争资源)
            PTHREAD_SCOPE_SYSTEM   (系统级竞争资源)。
                   
4.    线程堆栈的大小
           
            函数pthread_attr_setstackaddr和pthread_attr_getstackaddr分别用来设置和得
            到线程堆栈的位置。
           
            int pthread_attr_getstacksize(const pthread_attr_t *,size_t * stacksize);
            int pthread_attr_setstacksize(pthread_attr_t *attr ,size_t *stacksize);
            参数:attr                线程属性变量
                    stacksize        堆栈大小
            若成功返回0,若失败返回-1。           
       
5.    线程堆栈的地址           
           
            #include
            int pthread_attr_getstackaddr(const pthread_attr_t *attr,void **stackaddf);
            int pthread_attr_setstackaddr(pthread_attr_t *attr,void *stackaddr);
            参数:attr               线程属性变量
                    stackaddr     堆栈地址           
            若成功返回0,若失败返回-1。
           
            注意:pthread_attr_getstackaddr已经过期,现在使用的是:pthread_attr_getstack

6.    警戒缓冲区
           
            函数pthread_attr_getguardsize和pthread_attr_setguardsize分别用来设置和得
            到线程栈末尾的警戒缓冲区大小。

            #include                    
            int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,size_t *restrict
            guardsize);
            int pthread_attr_setguardsize(pthread_attr_t *attr ,size_t *guardsize);
            若成功返回0,若失败返回-1。
           
            值得注意:
                        线程属性guardsize控制着线程栈末尾之后以避免栈溢出的扩展内存
                        大小。这个属性默认设置为PAGESIZE个字节。可以把guardsize线
                        程属性设为0,从而不允许属性的这种特征行为发生:在这种情况
                        下不会提供警戒缓存区。同样地,如果对线程属性stackaddr作了
                        修改,系统就会认为我们会自己管理栈,并使警戒栈缓冲区机制无
                        效,等同于把guardsize线程属性设为0。

 

一.函数:

1.线程属性的初始化与销毁:
#include
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t   *attr);

Both return: 0 if OK, error number on failure
2.设置线程属性--detackstate(分离状态):
#include
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr, int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr,  int detachstate);

Both return: 0 if OK, error number on failure
detachstate有两个选项:PTHREAD_CREATE_DETACHED 分离状态启动线程
                       PTHREAD_CREATE_JOINABLE 正常状态启动线程
3.设置线程属性--stackaddr(线程栈的最低地址),stacksize(线程栈的大小):
#include
int pthread_attr_getstack(const pthread_attr_t *restrict attr,
                                                  void **restrict stackaddr,
                                                  size_t *restrict stacksize);
int pthread_attr_setstack(const pthread_attr_t *attr,
                                                  void *stackaddr,  
                                                  size_t *stacksize);

Both return: 0 if OK, error number on failure
4.设置线程属性--stacksize(线程栈的大小):
#include
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,
                                                          size_t *restrict stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr,
                                                          size_t stacksize);

Both return: 0 if OK, error number on failure
5.设置线程属性--guardsize(线程栈末尾的警戒缓冲区大小)
#include
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,
                                                          size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr, 
                                                          size_t guardsize); 

Both return: 0 if OK, error number on failure
二.重点:

三.例子:

以分离状态创建线程

[cpp] view plaincopy
  1. #include   
  2. #include   
  3. #include   
  4.   
  5. void * thr_fn()  
  6. {  
  7.     printf("thread run\n");  
  8.     pthread_exit((void *)0);  
  9. }  
  10.   
  11. int main()  
  12. {  
  13.     pthread_t tid;  
  14.     pthread_attr_t attr;  
  15.     int ret;  
  16.   
  17.     ret = pthread_attr_init(&attr);  
  18.     if(ret!=0)  
  19.         printf("init attr error:%s\n",strerror(ret));  
  20.     ret = pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);  
  21.     if(ret==0)  
  22.     {  
  23.         ret = pthread_create(&tid,&attr,thr_fn,NULL);  
  24.         if(ret!=0)  
  25.             printf("create thread error:%s\n",strerror(ret));  
  26.     }  
  27.     pthread_attr_destroy(&attr);  
  28.     sleep(1);  
  29.     return 0;  
  30. }  
运行:
root@ubuntu1:~/12# ./a.out
thread run
1.基础线程创建:

#include
#include
#include

void * print_id( void * arg ) 
       //!> 这是线程的入口函数                               
{
    printf("The Current process is: %d \n", getpid());                                //!> 当前进程ID   
    printf( "The Current thread id : %d \n", (unsigned)pthread_self() );    //!> 注意此处输出的子线程的ID
}

int main( )
{
    pthread_t        t;
    int                 t_id;
   
    t_id = pthread_create( &t, NULL, print_id, NULL );        //!> 简单的创建线程
   
    if( t_id != 0 )                        //!> 注意创建成功返回0                               
    {
        printf("\nCreate thread error...\n");
        exit( EXIT_FAILURE );
    } 
    sleep( 1 );
    printf("\nThe Current process is: %d \n", getpid());                         //!> 当前进程ID       
    printf( "The Main thread id : %d \n", (unsigned)pthread_self() );    //!> 注意输出的MAIN线程的ID
    return 0;
}



2.测试线程的创建和退出

#include
#include
#include
#include

void * entrance_1( void * arg )                //!> 第一个创建的线程的入口函数
{
    printf( " thread 1 id == %d , run now ... \n", ( unsigned )pthread_self() );
    sleep( 3 );
    return ( ( void * ) 1 );
}

void * entrance_2( void * arg )                //!> 第二个创建的线程的入口函数
{
    printf( " thread 2 id == %d , run now ... \n", ( unsigned )pthread_self() );
    sleep( 3 );
    return ( ( void * ) 2 );
}

int main( )
{
    pthread_t        t1 = -1;    //!> 最好是初始化:因为下面的pthread_join是需要判断是否成功在输出的
    pthread_t        t2 = -1;
    int              tid1;
    int              tid2;
    void     *        ret;
   
    tid1 = pthread_create( &t1, NULL, entrance_1, NULL );    //!> 简单的创建线程
    tid2 = pthread_create( &t2, NULL, entrance_2, NULL );
   
    if( tid1 != 0 || tid2 != 0 )    //!> 创建线程失败                   
    {
        printf( "Create thread error...\n" );
        exit( EXIT_FAILURE );
    }
   
    if( t1 != -1 )                //!> 也就是线程还没有结束
    {
        if ( pthread_join( t1, &ret ) == 0 )    //!> join success
        {
            printf( " thread 1 get the return of pthread_join == %d \n", 2 );/ )
 {
     pthread_mutex_init( &mutex, NULL );        //!> 初始化为默认的互斥锁
    
     printf("主函数:创建2个子线程...\n");
    
     create_two_thread();        //!> 创建2个线程
    
     printf("主函数:等待线程完成任务...\n");
    
     wait_two_thread();        //!> 等待线程完成任务
                                 //!> 线程任务完成才可以执行下面代码
     printf("线程任务完成...\n");
    
     printf("Num == %d \n\n", num);
    
     return 0;
 }
 
4.双线程处理:冒泡排序算法

//        双线程处理冒泡排序(多线程也一样)
//        实现从“小”--->“大”排序

#include
#include
#include
#include

int g_arr[] = { 10, 23, 12, 34, 5, 29, 90, 9, 78, 44 };        //!> 全局的要排序的数组
pthread_t                thread[2];               //!> 两个线程
pthread_mutex_t        mutex;                //!> 互斥锁

int                         g_i = 0;                     //!> 全局的剩余排列次数

//!> 打印数组
void print_array()
{
    int i;
    for( i = 0; i < 10; i++ )
    {
        printf( " %d ", g_arr[i] );
    }
    printf("\n");
}

//!> 交换元素
void swap_elem( int * a, int * b )
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

//!> 线程1入口函数
void * entrance_1( void * arg )
{
    int j;
    for( g_i = 0; g_i < 10; g_i++ )    //!> 外层循环
    {
        pthread_mutex_lock( &mutex );        //!> 加锁
       
        printf( "线程1后台执行排序...\n" );
       
        for( j = 0; j < ( 10 - g_i - 1 ); j++ )    //!> 内层循环
        {
            if( g_arr[j] > g_arr[j+1] )
            {
                swap_elem( &g_arr[j], &g_arr[j+1] );
            }
        }
       
        pthread_mutex_unlock( &mutex );    //!> 解锁
       
        sleep( 1 );
    }
}

//!> 线程2入口函数
void * entrance_2( void * arg )
{
    int j;
    for( g_i = 0; g_i < 10; g_i++ )    //!> 外层循环
    {
        pthread_mutex_lock( &mutex );        //!> 加锁
       
        printf( "线程2后台执行排序...\n" );
           
        for( j = 0; j < ( 10 - g_i - 1 ); j++ )    //!> 内层循环
        {
            if( g_arr[j] > g_arr[j+1] )
            {
                swap_elem( &g_arr[j], &g_arr[j+1] );
            }
        }
   
        pthread_mutex_unlock( &mutex );    //!> 解锁

        sleep( 2 );   
    }
}

//!> 创建2个线程
void create_two_thread()
{
    memset( &thread, 0, sizeof( thread ) );            //!> 初始化为0(作为下面的判断进程是否创建OK依据)
   
    if( ( pthread_create( &thread[0], NULL, entrance_1, NULL ) ) == 0 )
    {
        printf("线程1创建OK ...\n");
    }
    else
    {
        printf("线程1创建Error ...\n");
        exit( EXIT_FAILURE );
    }
   
    if( ( pthread_create( &thread[1], NULL, entrance_2, NULL ) ) == 0 )
    {
        printf("线程2创建OK ...\n");
    }
    else
    {
        printf("线程2创建Error ...\n");
        exit( EXIT_FAILURE );
    }
   
}

//!> 线程执行与等待
void do_and_wait()
{
    if( thread[0] != 0 )//!> 由于在create_two_thread中初始化=0,if床架ok,那么不可能还是0
    {
        pthread_join( thread[0], NULL );    //!> 等待线程1结束,不结束不执行下面代码
        printf("线程1执行结束退出...\n");
    }
    else
    {
        printf("线程1创建Error...\n");
        exit( EXIT_FAILURE );
    }
   
    if( thread[1] != 0 )
    {
        pthread_join( thread[1], NULL );    //!> 等待线程1结束,不结束不执行下面代码
        printf("线程2执行结束退出...\n");
    }
    else
    {
        printf("线程2创建Error...\n");
        exit( EXIT_FAILURE );
    }
   
}

int main( )
{
    printf("主函数:下面创建2个线程共同处理冒泡排序...\n");
   
    pthread_mutex_init( &mutex, NULL );
   
    print_array();                //!> 打印排序前的结果
   
    create_two_thread();        //!> 创建线程
    do_and_wait();            //!> 执行线程and等待
   
    printf( "排序完成:\n" );

    print_array();                //!> 打印排序后的结果
       
    return 0;
}

5.线程清理处理程序

//        线程清理处理程序TEST

#include
#include
#include

void clean(void *arg)
{
    printf("清理: %s \n", (char *)arg);
}

void * entrance( void * arg )
{
    printf("线程开始...\n");
   
    pthread_cleanup_push( clean, "线程处理程序1" );
    pthread_cleanup_push( clean, "线程处理程序2" );
   
    printf("pthread clean 完成...\n");
   
    sleep(3);
   
    pthread_exit((void *)0);        //!> 我们知道:清理函数只有在异常退出时候才会做一些清理工作
                                                //!> 所以此处的退出是异常退出来测试的!
   
    pthread_cleanup_pop(0);    
    pthread_cleanup_pop(0);    
   
   
   
}

int main( )
{
    pthread_t     tid;
    void     *     ret = NULL;
   
    if( ( pthread_create( &tid, NULL, entrance, (void *)1 ) ) != 0 )
    {
        printf("创建线程失败...\n");
        exit( EXIT_FAILURE );
    }
   
    pthread_join( tid, &ret );       
   
    if( ret )                            //!> 注意此处相当于是抛出异常
    {                                //!> 避免子线程的异常退出造成的空指针情况
        printf( "结束:code == %d\n", *( ( int * ) ret) );
    }
   
    return 0;
}

/////////////////////////////////////////////////////////////
//    DEMO——2

#include
#include
#include

void clean( void * arg )
{
    printf("清理函数执行...\n");
}

void * entrance( void * arg )
{
    int old_type, old_state;
    int i = 0;
   
    pthread_cleanup_push( clean, NULL );    //!> 设置清理函数
    printf("下面设置对本线程的“取消”无效\n");
    pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &old_state );
                                                //!> 设置对本线程的“取消”无效
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&old_type);    //>>>>>>>>>>> 目标句1
   
    while( 1 )
    {
        ++i;
        printf("子线程runing...\n");
        sleep( 2 );
        if( 5 == i )
        {
            printf("下面取消设置对本线程的“取消”无效\n");
            pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_state);
        }
    }
   
    pthread_cleanup_pop( 0 );
}

int main( int argc, char ** argv )
{
    pthread_t    tid;
    int               res;
    void *          ret;
   
    pthread_create( &tid, NULL, entrance, NULL );
    sleep( 2 );
    printf("请求子线程退出...\n");
    pthread_cancel( tid );            //!> 请求子线程退出
   
    res = pthread_join( tid, &ret );    //!> 等待子线程退出
   
    if( ret != PTHREAD_CANCELED )    //!> 非安全退出
    {
        printf("pthread_join 失败...\n");
        exit( EXIT_FAILURE );
    }
    else
    {
        printf("Success..");
    }
       
    exit( EXIT_SUCCESS );   
}


    分析:
        没有加上“目标句”的结果是:
                                        下面设置对本线程的“取消”无效
                                        子线程runing...
                                        请求子线程退出...
                                        子线程runing...
                                        子线程runing...
                                        子线程runing...
                                        子线程runing...
                                        下面取消设置对本线程的“取消”无效
                                        子线程runing...                        //!> 比下面多的
                                        清理函数执行...
                                        Success..                                //!> 与下面不一样的
                                       
      加上后:
                                        下面设置对本线程的“取消”无效
                                        子线程runing...
                                        请求子线程退出...
                                        子线程runing...
                                        子线程runing...
                                        子线程runing...
                                        子线程runing...
                                        下面取消设置对本线程的“取消”无效
                                        清理函数执行...
                                        pthread_join 失败...                    //!> 与上面不一样的
                                       
       这句的作用是将取消类型设置为PTHREAD_CANCEL_ASYNCHRONOUS,即取消请求会被立即响应                                   
       那么就不会再次进入等待下一个“取消点”再进行取消!!!
   
       注意:if在while中没有sleep,那么程序会无限run,我们知道sleep是相当于是释放一下线程,那么此时的主线程中的cancel信号又被接收到,那么if本函数可以响应了,那么就cancle了,if将sleep去掉,那么死循环!再次解释:所谓pthread_cancel仅仅是请求某个线程退出,那么究竟是不是退出还要看state和type的设置!                   



6. pthread_once 工作原理code


//        pthread_once 函数使用

#include
#include
#include

pthread_once_t        once = PTHREAD_ONCE_INIT;    //!> once宏赋值

//!> 初始化执行函数
void once_init( void )
{
    printf("初始化成功! 我的ID == %d\n", (unsigned)pthread_self());
}

//!> 线程入口函数
void * entrance( void * arg )
{   
    printf("子线程:ID == %d \n", (unsigned)pthread_self());
    //!> once = PTHREAD_ONCE_INIT;        //!> 测试使用(下面的要求)
    pthread_once( &once, once_init );        //!> 此处也有初始化
}

//!> main函数
int main( int argc, char * argv[] )
{
    pthread_t        pid;
   
    pthread_create( &pid, NULL, entrance, NULL );

    printf("主函数ID == %d \n", (unsigned)pthread_self());
   
//!>    pthread_join( pid, NULL );            //!> 分析点
   
    pthread_once( &once, once_init );    //!> 调用一次初始化函数
   
    pthread_join( pid, NULL );
   
    return 0;
}

        if pthread_join是在主函数初始化后面,那么就是主函数初始化的
        结果是: 主函数ID == 441960192
                       初始化成功! 我的ID == 441960192
                      子线程:ID == 433944320
        显而易见是主函数初始化的!
       
        if pthread_join是在之前,那么就是要等待子函数执行ok后才执行自己的下面代码
        但是此时已经初始化ok了,所以不在初始化!
        结果是:主函数ID == 210818816
                      子线程:ID == 202802944
                      初始化成功! 我的ID == 202802944
        显然是子函数执行的初始化!

        本质:   其实就是操作once变量而已,与互斥变量的本质是一样的!!!
                      我们可以这样测试在entrance中加入once = PTHREAD_ONCE_INIT;
        结果是:主函数ID == 1590228736
                      初始化成功! 我的ID == 1590228736
                      子线程:ID == 1582212864
                      初始化成功! 我的ID == 1582212864
       
        感兴趣的可以使用pthread_mutex_t 的互斥变量处理,效果一样!
        还有最最简单的就是bool值处理!此处不建议!

7.pthread_key_create线程键 与线程存储

#include
#include
#include

pthread_once_t        once = PTHREAD_ONCE_INIT;
pthread_key_t        key;            //!> 键值
   
int                         g_val = 10;    //!> 传说中的独享值,呵呵

void once_init_key()
{   
    if( pthread_key_create( &key, NULL ) == 0 )    //!> 创建线程键值
    {                                                    //!>
        printf("创建线程键OK ...\n");
    }
}

void * entrance( void * arg )
{
    int * val;
    printf("子线程:ID == %d \n", (unsigned)pthread_self());
   
    pthread_setspecific( key, &g_val );               //!> 将 g_val 作为一个每个进程的独享值
   
    val = ( int * )pthread_getspecific( key );        //!> 取出那个值
                                                        //!> 此后对于这些量都有自己的处理方式,
                                                        //!> 名称相同但是内容不同!!!   
                                                        //!> 对于文件的处理是最好的!!!
    printf("ID == %d, Value == %d\n",  (unsigned)pthread_self(), *val);
}


int main( )
{
    pthread_t        tid, tid2;
    void     *      ret1;
    void     *     ret2;
   
    if( pthread_create( &tid, NULL, entrance, NULL ) != 0 )            //!> 线程1
    {
        printf("创建线程1失败...\n");
        exit( EXIT_FAILURE );
    }
   
    if( pthread_create( &tid2, NULL, entrance, NULL ) != 0 )            //!> 线程2
    {
        printf("创建线程2失败...\n");
        exit( EXIT_FAILURE );
    }
           
    printf("主函数:ID == %d \n", (unsigned)pthread_self());
       
    //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   
   
    pthread_once( &once, once_init_key );    //!> 创建一个键值
   
    //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
   
    printf("下面等待子线程执行ok... \n");
   
    pthread_join( tid, &ret1 );                    //!> 等待线程( 必不可少 )
    pthread_join( tid2, &ret2 );
   
    return 0;
}

结果:
    主函数:ID == 1588877056
    创建线程键OK ...
    子线程:ID == 1580861184
    ID == 1580861184, Value == 10
    下面等待子线程执行ok...
    子线程:ID == 1572468480
    ID == 1572468480, Value == 10

 

0 0