linux多线程-懒人笔记(二)

来源:互联网 发布:linux acl chown 区别 编辑:程序博客网 时间:2024/05/18 01:23

这一目记录我对linux多线程的继续学习。

条件变量学习

这一块的学习围绕两个问题来,本质都是针对下面这个接口:

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

问题1cond为什么要结合wait一起用?

1.这么考虑,这个问题要说清楚。必须从为什么要引入条件变量这个东西说起。
2.那么,我们先这样考虑,如果没有条件变量的时候,会怎么样?考虑如下的情形:
问题:一个简单的生产者消费者模型,卖票-补票。生产者在票数为0的时候进行补票,消费者在票数不为0的时候消费。用ticket_num = 100;来表示这个共享资源。

伪代码如下:

// producerwhile(1){    pthread_mutex_lock(&mtx);    if( ticket_num > 0 ){        // no - op        // 票数为不空,什么都不做。    }    else{        // 票数为空,生产者补票        ticket_num += 10;    }    pthread_mutex_unlock(&mtx);}
// consumerwhile(1){    pthread_mutex_lock(&mtx);    if( ticket_num == 0 ){        // no-op        // 票数为空,什么都不做    else{        // 票数不为空,消费者消费        ticket_num -= 1;    }    pthread_mutex_unlock(&mtx);}

分析

上面这样的一段代码,可以实现生产者消费者模型,也就是说我们用互斥量和条件判断实现了生产者消费者模型。

但是,上面的模型存在一定问题!!!就是,忙等的情形!
对于生产者而言,在票数不为空的时候忙等
对于消费者而言,在票数为空的时候忙等!
由于系统的cpu非常宝贵,所以这样的忙等会降低cpu效率。

那么,一个自然的想法就是:能不能在忙等的时候,阻塞。把cpu资源让出来!可以暂时写成如下的形式:

// producerwhile(1){    pthread_mutex_lock(&mtx);    if( ticket_num > 0 ){        // 暂时先不考虑何时唤醒        block();    }    else{        // 票数为空,生产者补票        ticket_num += 10;    }    pthread_mutex_unlock(&mtx);}
// consumerwhile(1){    pthread_mutex_lock(&mtx);    if( ticket_num == 0 ){        // 暂时先不考虑何时唤醒        block();    else{        // 票数不为空,消费者消费        ticket_num -= 1;    }    pthread_mutex_unlock(&mtx);}

分析
上面的代码乍一看是没有问题,但是,实则不对。
首先考虑生产者,期初票数确实不为空,那么生产者阻塞是对的。可是,生产者阻塞之后并没有释放锁,带着锁就休眠了!试问,此时消费者还怎么去访问ticket_num,消费者会卡在mtx.lock()上面。从而进一步造成死锁!这也是,根本上为什么cond操作必须要带着锁的原因,就是在休眠前可以释放锁,让其他线程可以拿到锁去消费!

讲到这,我们也就清楚了。正确的操作,应该是:

  • 释放锁
  • 休眠

但是,上面的操作,还是有问题,如果,阻塞线程被唤醒之后,对于生产者而言,又要去更新ticket_num,此时,还是对公共资源的访问,肯定是需要加锁的,所以,生产着还需要再次拿到锁。所以,最终的操作如下

  • 释放锁
  • 休眠
  • 重新拿锁
// producerwhile(1){    pthread_mutex_lock(&mtx);    if( ticket_num > 0 ){        // 暂时先不考虑何时唤醒        pthread_mutex_unlock(&mtx);        block();        pthread_mtex_lock(&mtx);    }    else{        // 票数为空,生产者补票        ticket_num += 10;    }    pthread_mutex_unlock(&mtx);}
// consumerwhile(1){    pthread_mutex_lock(&mtx);    if( ticket_num == 0 ){        // 暂时先不考虑何时唤醒        pthread_mutex_unlock(&mtx);        block();        pthread_mtex_lock(&mtx);    else{        // 票数不为空,消费者消费        ticket_num -= 1;    }    pthread_mutex_unlock(&mtx);}

上面的代码,讲清楚了为什么cond要带着mtx的原因。如果不带着它,休眠后没有释放锁,其他线程无法访问公共资源,造成死锁。

当然,上面的代码也并不是完全正确,只是为了说明cond为什么要带着mtx操作。

问题2pthread_cond_wait的内部操作为什么是原子操作?

也就是上面的代码:

// producerwhile(1){    pthread_mutex_lock(&mtx);    if( ticket_num > 0 ){        // 暂时先不考虑何时唤醒        /* atomically        pthread_mutex_unlock(&mtx);        block();        */        /*after singal        pthread_mtex_lock(&mtx);        */    }    else{        // 票数为空,生产者补票        ticket_num += 10;    }    pthread_mutex_unlock(&mtx);}
// consumerwhile(1){    pthread_mutex_lock(&mtx);    if( ticket_num == 0 ){        // 暂时先不考虑何时唤醒        /* atomically        pthread_mutex_unlock(&mtx);        block();        */        /*after singal        pthread_mtex_lock(&mtx);        */    else{        // 票数不为空,消费者消费        ticket_num -= 1;    }    pthread_mutex_unlock(&mtx);}

原因man手册里面已经说了:

   These functions atomically release mutex and cause the calling thread to block on the condition variable cond; atomically here  means   "atomically  with  respect  to  access by another thread to the mutex and then the condition variable". That is, if another thread is   able to acquire the mutex after the about-to-block thread has released it, then a  subsequent  call  to  pthread_cond_broadcast()  or   pthread_cond_signal() in that thread shall behave as if it were issued after the about-to-block thread has blocked.

如果release和block不是原子操作,会发生竞争条件。
比如,生产者释放锁之后,本来应该阻塞。但是,如果不用原子操作实现,那么在锁被释放之后,消费者把锁抢走了,并且买完了所有票,然后signal生产者,此时,生产者没有阻塞,所以没意义。最后消费者阻塞。但是,此时生产者才开始阻塞。那么此时构成了死锁。

假如是正常的情形,生产者释放锁,休眠一起完成。然后,此时消费者开始消费,消费完之后,signal生产者,生产者可以被正确唤醒。生产者,从lock函数拿锁返回,然后补票。程序正常执行。

最后正确代码如下:

// producerwhile(1){    pthread_mutex_lock(&mtx);    if( ticket_num > 0 ){        // 票数不为空,阻塞        pthread_cond_wait(&cond1, &pro);    }    else{        // 票数为空,生产者补票        // 唤醒消费者        ticket_num += 10;        pthread_cond_signal(&con);    }    pthread_mutex_unlock(&mtx);}
// consumerwhile(1){    pthread_mutex_lock(&mtx);    if( ticket_num == 0 ){        pthread_cond_signal(&pro);        pthread_cond_wait(&con, &mtx);    else{        // 票数不为空,消费者消费        ticket_num -= 1;    }    pthread_mutex_unlock(&mtx);}

代码

  • 互斥量实现同步
#include <stdio.h>#include <stdlib.h>#include <pthread.h>#define CONSUMER_NUM 3void* producer( void* );void* consumer( void* );void err_msg( const char* msg ){ perror(msg); exit(EXIT_FAILURE); }int ticket_num; pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t pro = PTHREAD_COND_INITIALIZER;pthread_cond_t con = PTHREAD_COND_INITIALIZER;int main( void ){    int ret = 0;    int i = 0;    pthread_t pro_id;    pthread_t con_id_arr[CONSUMER_NUM];    ticket_num = 10;    // create producer    ret = pthread_create( &pro_id, NULL, producer, NULL );    if( ret != 0 )        err_msg( "pthread_create()" );    // create consumer    for( i = 0; i < CONSUMER_NUM; ++i ){        ret = pthread_create( con_id_arr + i, NULL, consumer, NULL );        if( ret != 0 )            err_msg( "pthread_create()" );    }    // join producer    ret = pthread_join( pro_id, NULL );     if( ret != 0 )        err_msg( "pthread_join()" );    // join consumer    for( i = 0; i < CONSUMER_NUM; ++i ){        ret = pthread_join( *(con_id_arr + i), NULL);        if( ret != 0 )            err_msg( "pthread_join()" );    }    printf( "Thread has joined!\n" );    exit(EXIT_SUCCESS);}void* producer( void* arg){    while(1){        pthread_mutex_lock( &mtx );        if( ticket_num > 0 ){            // no-op        }        else{            ticket_num += 10;            printf( "%lu supply 10 tickets!\n", pthread_self() );        }        pthread_mutex_unlock( &mtx );    }}void* consumer( void* arg){    while(1){        pthread_mutex_lock( &mtx );        if( 0 == ticket_num ){            // no-op        }        else{            ticket_num--;            printf( "%lu buys a ticket, ticket_num = %d.\n", pthread_self(), ticket_num+1 );        }        pthread_mutex_unlock( &mtx );        sleep(1); // for fair    }}/*140189921945344 buys a ticket, ticket_num = 10.140189913552640 buys a ticket, ticket_num = 9.140189905159936 buys a ticket, ticket_num = 8.140189921945344 buys a ticket, ticket_num = 7.140189913552640 buys a ticket, ticket_num = 6.140189905159936 buys a ticket, ticket_num = 5.140189921945344 buys a ticket, ticket_num = 4.140189913552640 buys a ticket, ticket_num = 3.140189905159936 buys a ticket, ticket_num = 2.140189921945344 buys a ticket, ticket_num = 1.140189930338048 supply 10 tickets!140189913552640 buys a ticket, ticket_num = 10.140189905159936 buys a ticket, ticket_num = 9.140189921945344 buys a ticket, ticket_num = 8.140189905159936 buys a ticket, ticket_num = 7.140189913552640 buys a ticket, ticket_num = 6.140189921945344 buys a ticket, ticket_num = 5.140189905159936 buys a ticket, ticket_num = 4.140189913552640 buys a ticket, ticket_num = 3.140189921945344 buys a ticket, ticket_num = 2.140189905159936 buys a ticket, ticket_num = 1.140189930338048 supply 10 tickets!...*/
  • 条件变量结合互斥量实现同步
#include <stdio.h>#include <stdlib.h>#include <pthread.h>#define CONSUMER_NUM 3void* producer( void* );void* consumer( void* );void err_msg( const char* msg ){ perror(msg); exit(EXIT_FAILURE); }int ticket_num; pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t pro = PTHREAD_COND_INITIALIZER;pthread_cond_t con = PTHREAD_COND_INITIALIZER;int main( void ){    int ret = 0;    int i = 0;    pthread_t pro_id;    pthread_t con_id_arr[CONSUMER_NUM];    ticket_num = 10;    // create producer    ret = pthread_create( &pro_id, NULL, producer, NULL );    if( ret != 0 )        err_msg( "pthread_create()" );    // create consumer    for( i = 0; i < CONSUMER_NUM; ++i ){        ret = pthread_create( con_id_arr + i, NULL, consumer, NULL );        if( ret != 0 )            err_msg( "pthread_create()" );    }    // join producer    ret = pthread_join( pro_id, NULL );     if( ret != 0 )        err_msg( "pthread_join()" );    // join consumer    for( i = 0; i < CONSUMER_NUM; ++i ){        ret = pthread_join( *(con_id_arr + i), NULL);        if( ret != 0 )            err_msg( "pthread_join()" );    }    printf( "Thread has joined!\n" );    exit(EXIT_SUCCESS);}void* producer( void* arg){    while(1){        pthread_mutex_lock( &mtx );        if( ticket_num > 0 ){            // no-op            pthread_cond_wait(&pro, &mtx);        }        else{            ticket_num += 10;            pthread_cond_signal(&con);            printf( "%lu supply 10 tickets!\n", pthread_self() );        }        pthread_mutex_unlock( &mtx );    }}void* consumer( void* arg){    while(1){        pthread_mutex_lock( &mtx );        if( 0 == ticket_num ){            // no-op            pthread_cond_signal(&pro);            pthread_cond_wait( &con, &mtx );        }        else{            ticket_num--;            printf( "%lu buys a ticket, ticket_num = %d.\n", pthread_self(), ticket_num+1 );        }        pthread_mutex_unlock( &mtx );        sleep(1); // for fair    }}/*139919722161920 buys a ticket, ticket_num = 10.139919705376512 buys a ticket, ticket_num = 9.139919713769216 buys a ticket, ticket_num = 8.139919722161920 buys a ticket, ticket_num = 7.139919705376512 buys a ticket, ticket_num = 6.139919713769216 buys a ticket, ticket_num = 5.139919705376512 buys a ticket, ticket_num = 4.139919722161920 buys a ticket, ticket_num = 3.139919713769216 buys a ticket, ticket_num = 2.139919722161920 buys a ticket, ticket_num = 1.139919730554624 supply 10 tickets!139919722161920 buys a ticket, ticket_num = 10.139919713769216 buys a ticket, ticket_num = 9.139919722161920 buys a ticket, ticket_num = 8.139919713769216 buys a ticket, ticket_num = 7.139919722161920 buys a ticket, ticket_num = 6.139919713769216 buys a ticket, ticket_num = 5.139919722161920 buys a ticket, ticket_num = 4.139919713769216 buys a ticket, ticket_num = 3.139919722161920 buys a ticket, ticket_num = 2.139919713769216 buys a ticket, ticket_num = 1.139919730554624 supply 10 tickets!139919713769216 buys a ticket, ticket_num = 10.139919705376512 buys a ticket, ticket_num = 9.139919713769216 buys a ticket, ticket_num = 8.139919705376512 buys a ticket, ticket_num = 7.139919713769216 buys a ticket, ticket_num = 6.139919713769216 buys a ticket, ticket_num = 5.139919705376512 buys a ticket, ticket_num = 4.139919713769216 buys a ticket, ticket_num = 3.139919705376512 buys a ticket, ticket_num = 2.139919713769216 buys a ticket, ticket_num = 1.139919730554624 supply 10 tickets!139919713769216 buys a ticket, ticket_num = 10.139919722161920 buys a ticket, ticket_num = 9.139919722161920 buys a ticket, ticket_num = 8....*/

上面的代码基本上正确了,但是还有一个问题就是惊群效应的问题。按照我的理解,因为虽然消费者很多,但是只有一个可以阻塞在条件变量上(这个理解的准确性需要考虑)。所以,生产者唤醒之后,资源肯定会被刚才这消费着拿到。所以,上面的代码没有问题。但是,惊群效应的考虑,是及时你被唤醒了之后,资源也有可能被别人抢走,所以还需要再次判断。

#include <stdio.h>#include <stdlib.h>#include <pthread.h>#define CONSUMER_NUM 3void* producer( void* );void* consumer( void* );void err_msg( const char* msg ){ perror(msg); exit(EXIT_FAILURE); }int ticket_num; pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t pro = PTHREAD_COND_INITIALIZER;pthread_cond_t con = PTHREAD_COND_INITIALIZER;int main( void ){    int ret = 0;    int i = 0;    pthread_t pro_id;    pthread_t con_id_arr[CONSUMER_NUM];    ticket_num = 10;    // create producer    ret = pthread_create( &pro_id, NULL, producer, NULL );    if( ret != 0 )        err_msg( "pthread_create()" );    // create consumer    for( i = 0; i < CONSUMER_NUM; ++i ){        ret = pthread_create( con_id_arr + i, NULL, consumer, NULL );        if( ret != 0 )            err_msg( "pthread_create()" );    }    // join producer    ret = pthread_join( pro_id, NULL );     if( ret != 0 )        err_msg( "pthread_join()" );    // join consumer    for( i = 0; i < CONSUMER_NUM; ++i ){        ret = pthread_join( *(con_id_arr + i), NULL);        if( ret != 0 )            err_msg( "pthread_join()" );    }    printf( "Thread has joined!\n" );    exit(EXIT_SUCCESS);}void* producer( void* arg){     while(1){        pthread_mutex_lock( &mtx );        while( ticket_num > 0 ){// 条件判断写成while            // no-op            pthread_cond_wait(&pro, &mtx);        }        ticket_num += 10;        pthread_cond_signal(&con);        printf( "%lu supply 10 tickets!\n", pthread_self() );        pthread_mutex_unlock( &mtx );    }}void* consumer( void* arg){    while(1){        pthread_mutex_lock( &mtx );        while( 0 == ticket_num ){// 条件判断写成while            // no-op            pthread_cond_signal(&pro);            pthread_cond_wait( &con, &mtx );        }        ticket_num--;        printf( "%lu buys a ticket, ticket_num = %d.\n", pthread_self(), ticket_num+1 );        pthread_mutex_unlock( &mtx );        sleep(1); // for fair    }}/*139801458308864 buys a ticket, ticket_num = 10.139801466701568 buys a ticket, ticket_num = 9.139801449916160 buys a ticket, ticket_num = 8.139801458308864 buys a ticket, ticket_num = 7.139801466701568 buys a ticket, ticket_num = 6.139801449916160 buys a ticket, ticket_num = 5.139801458308864 buys a ticket, ticket_num = 4.139801466701568 buys a ticket, ticket_num = 3.139801449916160 buys a ticket, ticket_num = 2.139801458308864 buys a ticket, ticket_num = 1.139801475094272 supply 10 tickets!139801466701568 buys a ticket, ticket_num = 10.139801458308864 buys a ticket, ticket_num = 9.139801466701568 buys a ticket, ticket_num = 8.139801466701568 buys a ticket, ticket_num = 7.139801458308864 buys a ticket, ticket_num = 6.139801466701568 buys a ticket, ticket_num = 5.139801458308864 buys a ticket, ticket_num = 4.139801466701568 buys a ticket, ticket_num = 3.139801458308864 buys a ticket, ticket_num = 2.139801458308864 buys a ticket, ticket_num = 1.139801475094272 supply 10 tickets!139801449916160 buys a ticket, ticket_num = 10.139801458308864 buys a ticket, ticket_num = 9.139801449916160 buys a ticket, ticket_num = 8.139801458308864 buys a ticket, ticket_num = 7.139801449916160 buys a ticket, ticket_num = 6.139801458308864 buys a ticket, ticket_num = 5.139801449916160 buys a ticket, ticket_num = 4.*/

后记

说一点自己新的感悟,上面的代码。对于锁和互斥量各自的逻辑说的已经很清楚了,但是我想说的是。
用锁的原因是因为,生产者和消费者对ticket_num有竞争。用同步变量的原因是生产者和消费者在ticket_num上有两个同步语义。

但是,不见得所有的模型都是这样。比如,多生产者多消费者。生产者之间有竞争,他们要强一个buf去放任务。消费者之间也有竞争,他们要强一个buf去消费。然后,整个队列还有同步的语义。这点要分清楚。

原创粉丝点击