zoukankan      html  css  js  c++  java
  • linux C语言多线程库pthread中条件变量的正确用法逐步详解

    linux C语言多线程库pthread中条件变量的正确用法:

    了解pthread常用多线程API和pthread互斥锁,但是对条件变量完全不知道或者不完全了解的人群。

    关于条件变量的典型应用,可以参考非常精简的linux线程池实现(一)--使用互斥锁和条件变量。

    但是如果对条件变量不熟悉的请看本文。

    pthread库的条件变量机制的主要API有三个:

    int pthread_cond_wait(pthread_cond_t *cond , pthread_mutex_t *mutex);

    int ptheead_cond_broadcast(pthread_cond_t *cond);

    int pthread_cond_signal(pthread_cond_t *cond);

    注意:还有一个没说的API是pthread_cond_timedwait(),他跟pthread_cond_wait的唯一不同就是可以指定一个等待的超时时间,这里不对他做讨论。

    他们和其他几个pthread API 一起用于处理一种特定的情形的线程同步问题:

    1、若干个线程在某个条件没有满足时不能继续往下面走,于是纷纷调用pthread_cond_wait使自己在这个条件上陷入等待(休眠)。 

    2、当条件满足之后,另外有个活跃着的线程调用pthread_cond_broadcast()通知唤醒那些等待在这个条件上的线程,让他们继续往下执行。

    这种情形是非常基础的,很多更加具体的线程同步问题都是这种情形的扩展,比如说经典的消费者/生产者问题,读者/写者问题等等。明显“条件”是这种情形的核心,所以pthread得这套线程同步机制叫做“条件变量”。可以看出条件变量机制跟java的wait/notify费唱类似。

    条件变量通过 pthread_cond_t 数据类型来声明,而且使用之前要初始化:

    pthread_cond_t cond =PTHREAD_COND_INITIALIZER;

    上面这种是通过预定义的初始化宏来静态初始化,也可以用函数动态初始化:

      pthread_cond_t cond;

    pthread_cond_init(&cond,NULL);

    注意条件声明为全局可见的,因为条件变量会在多个线程的函数中被访问。条件变量最后不再使用了的时候,应该销毁。

    pthread_cond_destroy(&cond);

    在初始化后到销毁前这段时间内就是条件变量的正常生命周期了,可以按需要对他调用 pthread_cond_wait(),pthread_cond_signal(), pthread_cond_broadcast().

    pthread_cond_signal()的作用和pthread_cond_broadcast相似,但不同的是pthread_cond_signal会通知所有等待的线程中至少一个,让他们继续往下执行,而所有其他没被通知的等待的线程则继续等待(休眠)。之所以pthread_cond_wait()也可能因为各种原因自动返回,比如说信号中断。

    但是实际应用场景中我们每调用一次pthread_cond_signal 就唤醒一个等待线程:

    1
    某个线程专门负责从网络接收数据包,其它若干线程专门负责处理数据包。当没有任何数据包时,处理线程全部调用pthread_cond_wait陷入等待。当一个数据包到达时,接收线程调用phtread_cond_signal唤醒一个处理线程,处理线程拿走这个数据包去处理。当又一个数据包到达时,接收线程再次调用pthread_cond_signal唤醒一个线程……

    这个问题对信号量机制来说很容易,因为信号量中的sem_post函数只会唤醒一个等待的进程或线程。虽然,pthread_cond_signal本身不保证只唤醒一个等待线程,但是POSIX标准在定义这套API时候考虑了这个问题,他留了一个后门,让我们在应用程序中可以通过额外的代码来解决这个问题。

    先不考虑,那个后门,一个看上去可行的办法就是,除了条件变量意外,在额外设置一个全局的普通计数变量表示允许唤醒多少个等待的线程:

    int globle_count = 0;

    吗,那么当通知线程需要调用pthread_cond_signal唤醒别的线程之前,因该先增加全局变量的计数,表示允许唤醒的线程数目又增加了一个:

    gloable++

    pthread_cond_signal(&cond);

    pthread_cond_signal一旦调用,那些调用pthread_cond_wait等待在cond的线程可能会有好几个都会唤醒,假设全部唤醒了。其实我  们只想染一个继续往下走,其他的不应该往下走,那么其他那些等待线程就都应该再次调用pthread_cond_wait继续等待(这里应该有个循环)。

    下面的问题就是,怎么决定那个等待线程继续走哪?可以这样,当大家都被唤醒的时候,大家都判断一下 global_count 是不是大于0,也就是当前允不允许唤醒线程。如果某个等待线程检测到的gloable_count是大于0的,就赶紧把gloable_count减掉一个,然后自己往下走。这时候gloable_count少了一个,可能就是0了,表示不允许在唤醒线程,其他几个等待线程发现这一状况后不往下走了,再次调用pthread_cond_wait)表示继续等待:

    while(gloable<=0)

    {

    pthread_cond_wait(&cond,&mutex);

    }

    gloable -=1;

    到现在为止问题基本解决了,但是引出了一个新的问题:多个线程同时访问gloable_count变量会造成竞态条件。问题看上去很容易解决,使用互斥锁就好了。

    对于通知线程:

    pthread_mutex_lock(&mutex);

    gloable_count+=1;

    pthread_cond_signal(&cond);

    pthread_mutex_unlock(&mutex);

    对于等待线程:

    pthread_metex_lock(&mutex);

    while(gloable_count<=0)

    {

    pthread_cond_wait(&cond,&mutex);

    }

    gloable_count - =1;

    pthread_mutex_unlock(&mutex);

    新的问题又来啦:等待线程调用pthread_cond_wait()陷入等待时,还占有着mutex互斥锁,下次通知线程下次想要唤醒线程时就无法获取mutex互斥锁了,

    于是就会出现死锁。所以在调用pthread_cond——wait将当前线程陷入等待之前我们应该揭开互斥锁,当线程被唤醒时,从pthread_cond_wait函数返回时,我们因该重新获取互斥锁。

    比如像这样:

    pthread_mutex_lock(&mutex);

    while(gloable_count<=0)

    {

    pthread_mutex_unlock(&mutex);

    pthread_cond_wait(&cond,&mutex);

    pthread_mutex_lock(&mutex);

    }

    gloable_count-=1;

    pthread_mutex_unlock(&mutex);

    这段代码还是有问题的,在pthread_cond_wait函数调用前后当前线程都有一段看上去很短的不拥有mutex互斥锁的真空期,但是对于cpu来说这段真空期并不算太短。

    假设某个等线程检测到gloable_count == 0 ,于是解开mutex互斥锁,进入真空期,即将调用pthread_cond_wait()。就在这个时候,通知线程增加了一下gloable_count的技术值然后调用了pthread_cond_signal。接下来,刚才那个等待线程调用pthread_cond_wait陷入等待,由于pthread_cond_wait的调用发生在pthread_signal之后,所以pthread_cond_wait并不会返回。如果程序里的等待线程就这一个,这个通知就丢失了。

    问题到了这里似乎没路可走了,但是别忘了还有个后门没用上,那就是前面一直没提到的pthread_cond_wait 的第二个参数。第二个参数赫然是mutex!这下猜也能猜到这第二个参数是干什么的了,明显就是专门帮我们解开mutex锁啊。然后在pthread_cond_wait返回之前自动获取mutex锁。在这里顺道澄清一下,和条件变量关联的mutex,不是向网上部分人说到的那样是用来保护条件变量的,条件变量在实现的时候,是能够在实现的时候是能够做到线程安全的,因为他内部还有一个自己的互斥锁。

    所以正确的做法是:

    pthread_mutex_lock(&mutex);

    while(gloable_count<=0)

    {

    pthread_cond_wait(&cond,&mutex);

    }

    gloable_count-=1;

    pthread_mutex_unlock(&mutex);

    到这里问题是不是完全解决了?很遗憾,还差一点,pthread_cond_wait是线程撤销点之一,这意味着当某个线程因为调用pthread_cond_wait()而陷入休眠等待时,别的县城可以通过这个县城的id调用pthread_cancel让这个线程强制从pthread_cond_wait返回并开始执行一些清理工作,最后结束而退出。

    问题就出在pthread_cond_wait返回上,上面标红的地方已经被强调了,pthread_cond_wait返回之前会先自动获取mutex,也就是说返回以后已经占有了mutex互斥所。这种情况下,线程直接退出会导致互斥锁一直被占用,其他线程就无法获取这个互斥锁了,再次出现死锁。

    这个问题有两种解决办法:

    (1)线程退出前的清理工作中加入解开互斥锁的代码,这个并不难办到,用为POSIX定义了两个API:

    void pthread_cleanup_pop(int execute);

    void pthread_cleanup_push(void * (*routine)(void*),void * arg);

    pthread_cleanup_push(),用于像一个特殊栈压入一个函数指针,当线程退出时候,这个特殊站中的所有函数都会被一个个从堆栈顶弹出并执行(return 退出的情况除外)。pthread_cleanup_pop()用于从这个特殊堆栈的堆栈顶手动弹出函数指针,execute参数非0 ,弹出的函数会被自动执行。

    需要注意的是,POSIX标准允许这两个API被实现为带未闭合花括号的宏,所以这两个API一定(最好)要配套使用:它们必须一前一后(push在前),而且在同一个函数的同一个嵌套层次内。

    比如说这两个API的实现有可能会是类似于这样:

    所以这就是为什么它们的调用要求如此奇怪了。
    有了这两个API,想要解决刚才的问题,首先要定义一个清理回调函数:

    void mutex_clean(void * mutex)

    {

    pthread_mutex_unlock((pthread_mutex_t *) mutex);

    }

    然后在等待线程里调用那两个API:

    1. pthread_mutex_lock(&mutex);  

    2. pthread_cleanup_push(mutex_clean, &mutex);  

    3. while(global_count<=0) {  

    4.     pthread_cond_wait(&cond, &mutex);  

    5. }  

    6. global_count--;  

    7. pthread_cleanup_pop(0);  

    8. pthread_mutex_unlock(&mutex);  

    或者一个稍微简洁一些的写法:

    1. pthread_mutex_lock(&mutex);  

    2. pthread_cleanup_push(mutex_clean, &mutex);  

    3. while(global_count<=0) {  

    4.     pthread_cond_wait(&cond, &mutex);  

    5. }  

    6. global_count--;  

    7. pthread_cleanup_pop(1);  

    另外一种解决方案比这个麻烦一些,那就是设置mutex互斥锁的robust属性值为PTHREAD_MUTEX_ROBUST;

    对于robust互斥锁,当持有他的线程没解锁就退出以后,别的线程在去调用pthread_mutex_lock,函数会回一个EOWNERDEAD错误,线程检测到这这个错误后可以调用pthread_mutex_consistent使robust互斥锁恢复一致性,紧接着就可以调用phtread_mutex_unlock解锁了(尽管这个锁并不是当前这个线程加持的)。解锁完毕就可以重新调用pthread_mutex_lock了

    采用这种方案的时候,首先要声明一个全局可见的mutex属性变量:

    pthread_mutexattr_mutexattr

    然后初始化并设置属性值:

    pthread_mutexattr_init(&mutexaddr);

    pthread_mutexattr_setrobust(&mutexaddr,PTHREEAD_MUTEX_TOBUST);

    有了,mutex属性,接下来就是在初始化mutex的地方做修改了,通常我门对mutex的初始化都是,pthread_mutex_init(&mutex,NULL);

    现在改成,pthread_mutex_init(&mutex,&mutexaddr);

    现在准备工作已经完成了,开始干正事了。对于通知线程:

    while(EOWNERDEAD = pthread_mutex_lock(&mutex))

    {

    pthread_mutex_consistent(&mutex);

    pthread_mutex_unlock(&mutex);

    }

    gloable_count++;

    pthread_mutexunlock(&cond);

    pthread_mutex_unlock(&mutex);

    对于等待线程:

    while(EOWNEROEAD == pthread_mutex_lock(&mutex))

    {

    pthread_mutex_consistent(&mutex);

    pthread_mutex_unlock(&mutex);

    }

    while(gloable_count<=0)

    {

    pthread_cond_wait(&cond,&mutex);

    }

    gloable_count--;

    pthread_mutex_unlock(&mutex);

    到这里,所有的事情就完成了。

    #include <stdio.h>

    #include<pthread.h> //多线程所用头文件

    #include <semaphore.h> //信号量使用头文件

    pthread_cond_t g_cond /*=PTHREAD_MUTEX_INITIALIZER*/; //申明条锁,并用宏进行初始化

    pthread_mutex_t g_mutex ;

    //线程执行函数

    void threadFun1(void)

    {

    int i;

    pthread_mutex_lock(&g_mutex); //1

    pthread_cond_wait(&g_cond,&g_mutex); //如g_cond无信号,则阻塞

    for( i = 0;i < 2; i++ ){

    printf("thread threadFun1. ");

    sleep(1);

    }

    pthread_cond_signal(&g_cond);

    pthread_mutex_unlock(&g_mutex);

    }

    int main(void)

    {

    pthread_t id1; //线程的标识符

    pthread_t id2;

    pthread_cond_init(&g_cond,NULL); //也可以程序里面初始化

    pthread_mutex_init(&g_mutex,NULL); //互斥变量初始化

    int i,ret;

    ret = pthread_create(&id1,NULL,(void *)threadFun1, NULL);

    if ( ret!=0 ) { //不为0说明线程创建失败

    printf ("Create pthread1 error! ");

    exit (1);

    }

    sleep(5); //等待子线程先开始

    pthread_mutex_lock(&g_mutex); //2

    pthread_cond_signal(&g_cond); //给个开始信号,注意这里要先等子线程进入等待状态在发信号,否则无效

    pthread_mutex_unlock(&g_mutex);

    pthread_join(id1,NULL);

    pthread_cond_destroy(&g_cond); //释放

    pthread_mutex_destroy(&g_mutex); //释放

    return 0;

    }

    请大家,先看红颜色的1,2,明明是1先锁了互斥变量,但代码执行到2还是可以锁定,为什么会这样?

    pthread_cond_wait()什么情况才会解锁,继续跑下去。。。现在看一段典型的应用:看注释即可。

    问题解释:当程序进入pthread_cond_wait等待之后,将会把g_mutex进行解锁,当离开pthread_cond_wait之前,g_mutex会重新加锁。所以在main中的g_mutex会被加锁。

    我们还希望对g_count的最大值进行控制,比如希望它的最大值是10;那么当g_count等于10的时候,就要等待。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <unistd.h>
    #include <pthread.h>
     
    #define custom_count 2
    #define produce_count 3
     
    int nNum, nLoop;
    int g_count = 0;
     
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t cond = PTHREAD_MUTEX_INITIALIZER;
     
    void *consume(void *arg)
    {
        while (1)
        {
            pthread_mutex_lock(&mutex);
            while (g_count == 0)
            {
                printf("consume is waiting: %lu ", pthread_self());
                pthread_cond_wait(&cond, &mutex);
            }
            printf("consume is %lu, g_count is %d ", pthread_self(), g_count);
            g_count--;
            pthread_mutex_unlock(&mutex);
            sleep(1);
        }
        pthread_exit(NULL);
    }
     
    void *produce(void *arg)
    {
        while (1)
        {
            pthread_mutex_lock(&mutex);
            if (g_count >= 10)
            {
                printf("产品太多了,需要休眠1秒 ");
                pthread_mutex_unlock(&mutex);
                sleep(1);
                continue;
            }
            //不需要解锁再上锁,大于10,会解锁,会continue,不会执行下面语句
            printf("start produce the product ");
            g_count++;
            printf("produce is %lu, g_count is %d ", pthread_self(), g_count);
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&mutex);
            sleep(1);
        }
        pthread_exit(NULL);
    }
     
    int main()
    {
        int i = 0;
        pthread_t tidCustom[custom_count];
        pthread_t tidProduce[produce_count];
     
        //创建消费者线程
        for (i = 0; i < custom_count; i++)
        {
            pthread_create(&tidCustom[i], NULL, consume, NULL);
        }
        sleep(3);
        //创建生产者线程
        for (i = 0; i < produce_count; i++)
        {
            pthread_create(&tidProduce[i], NULL, produce, NULL);
        }
     
        //等待消费者线程
        for (i = 0; i < custom_count; i++)
        {
            pthread_join(tidCustom[i], NULL);
        }
     
        //等待生产者线程
        for (i = 0; i < produce_count; i++)
        {
            pthread_join(tidProduce[i], NULL);
        }
        printf("parent exit ");
        exit(0);
    }

    再举个例子:当有两个变量x,y需要在多线程之间同步并且学生要根据他们的大小来比较启动不同的县城执行顺序,这边用到了条件变量的技术:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    <span style="font-family:'KaiTi_GB2312';font-size:18px;"><strong>    #include <iostream>
        #include <pthread.h>
        using namespace std;
     
        pthread_cond_t qready = PTHREAD_COND_INITIALIZER;    //初始构造条件变量
        pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;    //初始构造锁
        pthread_t tid1,tid2,tid3;
     
        int x = 10;
        int y = 20;
     
     
        void *thrd_1(void *arg)
        {
            pthread_mutex_lock(&qlock);
            while(x<y)
            {
                pthread_cond_wait(&qready,&qlock);
            }
            pthread_mutex_unlock(&qlock);
            cout<<"1"<<endl;
            sleep(5);
        }
     
        void *thrd_2(void *arg)
        {
            pthread_mutex_lock(&qlock);
            x = 20;
            y = 10;
            cout<<"has change x and y"<<endl;
     
            pthread_mutex_unlock(&qlock);
            if(x > y)
            {
                pthread_cond_signal(&qready);
            }
            cout<<"2"<<endl;
        }
     
        void *thrd_3(void *arg)
        {
            pthread_join(tid1,NULL);
            cout<<"3"<<endl;
        }
     
        int main(int argc,char **argv)
        {
            int err;
            err = pthread_create(&tid1,NULL,thrd_1,NULL);
            if(err != 0)
            {
                cout<<"pthread 1 create error"<<endl;
            }
            err = pthread_create(&tid2,NULL,thrd_2,NULL);
            if(err != 0)
            {
                cout<<"pthread 2 create error"<<endl;
            }
            err = pthread_create(&tid3,NULL,thrd_3,NULL);
            if(err != 0)
            {
                cout<<"pthread 3 create error"<<endl;
            }
            while(1)
            {
                sleep(1);
            }
            return 0;
     
        }</strong></span>

    可以看到,创建了3个线程后,执行顺序2,1,3,为什么是这个顺序?我们接下来来看,当创建tid1线程的识货,进入线程函数,并且加上了锁,然后进入pthread_cond_wait函数,这个函数的功能是等等待cond这个变量能够成功,这个条件是什么?我们稍后再看,现在我们知道,这个函数,在cond条件没有满足的时候回卡在这里,并且会吧传入的互斥锁解锁,为什么要解锁的,你可以想想,如果不解锁的话,那外部就没有可以对x,y的修改权,因为其他两个线程想要修改这两个值的话都需要对cond进行加锁。

    好了线程1就是这样,那之后就会运行线程2,我们看线程2的线程函数,该函数一开始也加了锁,但当线程1的pthread_cond_wait(&cond,&mutex);解锁之后,他就可以继续运行了,并且,在之后,他对x,y,进行了修改,改好之后解锁,并且调用了pthread_cond_signal(&cond);通知线程1,现在可以知道了吧。这个满足条件就是要x>y。

        现在有个问题,一定是要在发送通知之前进行解锁吗?是肯定的,因为如果先发送通知信号给线程1的时候,pthread_cond_wait可能在线程2解锁之前就返回,而当他返回的时候,会再次将这个锁锁定,而这个锁还没有在线程2中解锁,因此会使其在次卡住。虽然这个卡住在线程2运行到解锁处会消除,但这并不符合我们又是的需求,所以最好实在解锁之后在发送信号。

    为允许在线程或进程间共享数据,同步通常是必须的。常见的同步方式有:互斥锁、条件变量、读写锁、信号量。

    另外,对于进程间的同步,也可以通过进程间通信的方式进行同步,包括管道(无名管道、有名管道)、信号量、消息队列、共享内存、远程过程调用,当然也可以通过Socket来进行网络控制。

    一、互斥锁和条件变量是同步的基本组成部分。

        互斥锁和条件变量出自POSIX线程标准,多用来同步一个进程中的各个线程。但如果将二者存放在多进程共享的内存区中,它们也可以用来进行进程间的同步。

       1、互斥锁用于保护临界区,以保护任何时刻只有一个线程在执行其中的代码,其大体轮廓如下:

        lock_the_mutex()

        临界区

        unlock_the_mutex()

    下列三个函数给一个互斥锁上锁和解锁:

    #inlcude<pthread.h>

    int pthread_mutex_lock(pthread_mutex_t * ptr);//都是不能立刻获得锁,将阻塞在此处

    int pthread_mutex_trylock(pthread_mutex_t *ptr);//若是不能立刻获得锁,将返回EBUSY,用户可以根据此返回值做其他操作,非阻塞模式:

    int pthread_mutex_unlock(pthrea_mutex_t *ptr);//释放锁

    互斥锁通常用于保护有多个线程或多个进程分享的共享数据(share data)

    2、条件变量,他是发送信号与等待信号。互斥锁用户上锁,条件变量则用于等待。一般来说,在一个进程/线程进行某种操作,当某种条件成立时,调用pthread_cond_signal()来发送信号,从而使pthread_cond_wait()返回。此处要注意的是,这里所谈到的信号,不是系统级别的SIGXXXXXX信号,只是用信号这个词语更容易理解。条件变量与信号量更接近或者就可以认为是信号量。

    下列两个函数用来对条件变量进行控制:

    #inculde<pthread.h>

    int pthread_cond_wait(pthread_cond_t *ptr,pthread_mutex_t *mptr);

    int pthread_cond_signal(pthread_cond_t *ptr);

        有上述代码可以看出,条件变量的使用时需要结合所机制的的,继上面提到的互斥锁,就是所,一个进程,线程,要等到临界区的共享数据达到某种状态时在进行某种操作,而这个状态成立,则是由另一个进程,线程来完成后发送信号来通知的。

        其实想一想,pthread_cond_wait()函数也可以用一个while死循环来等待条件的成立,但是要注意的是,使用while死循环会严重消耗cpu,而pthread_cond_wait则是采用线程睡眠的方式,它是一种等待模式,而不是一直的检查模式。

        总的来说,给条件变量发送信号的代码大体如下:

    pthread_mutex_lock(&mutex);

    设置条件为真

    pthread_mutex_unlock(&mutex);

    pthread_cond_signal(&cond);//发送信号

    等待并进入睡眠以等待条件变为真的代码如下;

    pthread_mutex_lock(&mutex);

    while(条件为假)

    {

    pthread_cond_wait(&cond,&mutex);

    }

    执行某种操作

    pthread_mutex_unlock(&mutex);

    在这里需要注意的是,pthread_cond_wait(&cond,&mutex)是一个原子操作,当他执行时,首先对mutex解锁,这样另外的线程才能得到锁来修改条件,pthread_cond_wait解锁后,再将本身的线程进入睡眠,另外,当该函数返回时,会在对mutex进行枷锁,这样才能“执行某种操作”后unlock锁。

    二、读写锁:

        顾名思义,读写锁,也是一种所,他是在互斥锁的基础上进行了改进,当一个线程获得写入锁时,其他线程任然可以获得所,只不过获得的锁是读取锁,因为一个线程写入,不影响其他进程的操作。

    三、信号量:

    英文:semaphore,他是一种专门用于提供不同线程间,同步手段的源于。

       进程A                               进程B

                        /

         进程             /

            ----------------------------------------------------

         内核           /

                  信号量


    也就是说,信号量是由内核来维护的,他独立出进程。因此可以通过它来进行同步。

    上图一般来说,是基于Posix有名信号量,可以认为它是系统中的一个特殊文件(因为在Linux中,一切都可以认为是文件),因为在进程间的通信、同步中用的比较多,如果是线程之间的同步,经常用基于Posix内存的信号量。(基于内存的信号量必须在创建时指定是否在进程间共享,有名信号量随内核有持续性,需手工删除,而基于内存的信号量具有随进程的持续性)

      对于信号量的工作原理,其实和互斥锁+条件变量相似。

      主要函数有:sem_open、sem_close、sem_unlink,这里要注意,close只是关闭信号量,但并未从系统中删除,而unlink是删除该信号量。

      sem_wait和sem_trywait函数,他们和pthread_cond_wait功能相似,都是等待某个条件的成立,sem_wait和sem_trywait的区别是,当所指定的信号量的值为0时,后者并不将调用者投入睡眠,而是立刻返回EAGAIN,即重试。

      sem_post和sem_getvalue函数,sem_post将指定的信号量加一,然后唤醒正在等待该信号量值变为正数的任意线程。sem_getvalue是用来获取当前信号量值的函数。

     

    总结:互斥锁,条件变量,信号量三者的区别:

    (1)互斥锁必须是又给他上锁的线程解锁,信号量没有这种限制:一个线程等待某个信号量,而另一个线程可以刮出该信号量

    (2)每个信号量有一个与之关联的值,挂出时+1,等待是-1,那么任何线程都可以挂出一个信号,即使没有现成在等待该信号的值。不过对于条件变量来说,如果pthread_cond_signal之后没有任何线程阻塞在pthread_cond_wait()上,那么次条件变量上的信号丢失。

    (3)在各种各样的同步技巧中,能够从信号处理程序中安全调用的唯一函数是sem_post

     

  • 相关阅读:
    [转]在Windows 7 X64系统中安装SharePoint 2010
    使用CUBE和ROLLUP对数据进行汇总
    SQL Server、Oracle数据库排序空值null问题解决办法
    解释传统与敏捷方法最贴切的故事:大象与猴子
    3个简单的问题教会你如何做人、做事、做学问
    DOS命令行方式使用FTP实战练习
    SQL四种语言:DDL,DML,DCL,TCL
    如何对软件需求进行测试
    Windows中的句柄(handle)
    软件静默安装参数
  • 原文地址:https://www.cnblogs.com/yjds/p/8598881.html
Copyright © 2011-2022 走看看