Unix网络编程源码解析_进程间通信IPC之生产-消费者问题

一、生产者-消费者问题(非涉及同步),主要介绍线程函数的作用;

全局变量定义

int nitems; /* read-only by producer and consumer */
struct {
  pthread_mutex_t mutex;
  int buff[MAXNITEMS];
  int nput;
  int nval;
}shared = {PTHREAD_MUTEX_INITIALIZER};

void *produce(void *);
void *consume(void *);

main函数如下

int main(int argc, char **argv){

        /* 最多可容纳MAXNTHREADS个线程(实际没用到100个),count[i]数组用来存放第i个线程所执行的次数 */
        int i, nthreads, count[MAXNTHREADS];

        /* tid_produce[i]用来存放第i个线程的的标识 */
        pthread_t tid_produce[MAXNTHREADS], tid_consume;
       
        /* items表示生产的总条目 threads表示将有多少个线程来生产 */
        if (argc != 3)
                err_quit("usage: prodcons2 <#items> <#threads>");

        nitems = min(atoi(argv[1]), MAXNITEMS);/* 命令行参数1(生产的总条目)与10000作比较,取最小值 */
        nthreads = min(atoi(argv[2]), MAXNTHREADS);/* 命令行参数2(线程数)与100作比较,取最小值 */
    
        /* Linux标准调用pthread_setconcurrency()函数 */
        Set_concurrency(nthreads);
/* ------------------------------------------------开始生产------------------------------------------------------------------ */
        /* 创建生产线程,&tid_produce[i]为线程标识地址,tid_produce[i]为线程标识的值,&count[i]为传入地址,count[i] 统计第i个线程所执行的次数(值) */
        for (i = 0; i < nthreads; i++) {
                count[i] = 0;
                Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
        }

        /*pthread_join()作用是等tid_produce[i]的线程执行完,即阻塞运行,如果线程不执行完,我就不继续往下执行*/
        for (i = 0; i < nthreads; i++) {
                Pthread_join(tid_produce[i], NULL);
                printf("count[%d] = %d\n", i, count[i]);
        }
/* ------------------------------------------------生产完成-------------------------------------------------------------------- */
        /* 创建一个消费线程*/
        Pthread_create(&tid_consume, NULL, consume, NULL);
        Pthread_join(tid_consume, NULL);

        exit(0);
}

其中Set_concurrency()的函数,调用的函数原型是pthread_setconcurrency(),其作用是让系统知道并发运行的线程数量,这样每个生产的线程都会执行到;资料介绍如果在某些系统(Slaris)下省略该调用,会导致只有一个生产线程 执行;

生产者函数如下

static int num = 1;
/*只打印一次,查看shared.nput与shared.nval的初值,通过程序执行为0
void *produce(void *arg){
        for ( ; ; ) {
                 /* 加锁 */
                Pthread_mutex_lock(&shared.mutex);
                /* nitems 为要生产的总条目,为命令行参数1,演示输入的1000000*/
                if (shared.nput >= nitems) {
                        /* 解锁 */
                        Pthread_mutex_unlock(&shared.mutex);
                        return(NULL);
                }
                if(num > 0){
                        printf("shared.nput: %d--,shared.nval:
 %d--\n",shared.nput,shared.nval);
                        num--;
                }
                shared.buff[shared.nput] = shared.nval;//对应buff[i]存放的值为i
                shared.nput++;
                shared.nval++;
                /* 解锁 */
                Pthread_mutex_unlock(&shared.mutex);
                *((int *) arg) += 1; 传入参数count[i]的值累加,i表示第i个线程,分配的空间在main函数中
        }
}

消费者函数如下:

void *consume(void *arg){
        int i;
        /* 对生产线程所生产的 nitems 条目进行检查*/
        for (i = 0; i < nitems; i++) {
                if (shared.buff[i] != i)
                        printf("buff[%d] = %d\n", i, shared.buff[i]);
        }
        return(NULL);
}

程序演示结果

Unix网络编程源码解析_进程间通信IPC之生产-消费者问题_第1张图片

以上为先用多个线程生产(多个线程访问共享数据时加锁),后用一个线程进行消费,没有涉及到同步问题,后续的同步问题都时在该程序基础上进行,所以有必要了解基本的源代码;

二、与一相比,变化的有两处,main函数和消费者函数

源码分析,与上面的程序相比,函数pthread_setconcurrency()调用的参数增加了1个,即并行执行的线程为生产和消费线程总数,消费线程执行的顺序改变了,基本上与生产线程一并启动;

int main(int argc, char **argv){

        int             i, nthreads, count[MAXNTHREADS];
        pthread_t       tid_produce[MAXNTHREADS], tid_consume;

        if (argc != 3)
                err_quit("usage: prodcons3 <#items> <#threads>");
        nitems = min(atoi(argv[1]), MAXNITEMS);
        nthreads = min(atoi(argv[2]), MAXNTHREADS);

        Set_concurrency(nthreads + 1);
        for (i = 0; i < nthreads; i++) {
                count[i] = 0;
                Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
        }
    /* 与上面的相比,消费线程启动提前(基本与生产线程一起执行)*/
        Pthread_create(&tid_consume, NULL, consume, NULL);

        for (i = 0; i < nthreads; i++) {
                Pthread_join(tid_produce[i], NULL);
                printf("count[%d] = %d\n", i, count[i]);
        }
        Pthread_join(tid_consume, NULL);

        exit(0);
}

生产者的线程执行函数保持不变,改变的是消费者线程;

消费者线程执行如下

void consume_wait(int i){
        /*等待第i个条目是否生产完成,如果生产完成了就return;如果生产没完成,就while(1)执行[加锁、解锁]动作 (等待生产完成),该方法俗称轮转(spinning)或者轮询(polling)*/
        for ( ; ; ) {
                Pthread_mutex_lock(&shared.mutex);
                if (i < shared.nput) {
                        Pthread_mutex_unlock(&shared.mutex);
                        return;                 
                }
                Pthread_mutex_unlock(&shared.mutex);
        }
}

void * consume(void *arg){
        int     i;

        for (i = 0; i < nitems; i++) {
                consume_wait(i);/*等待第i个条目是否生产完成*/
                if (shared.buff[i] != i)
                        printf("buff[%d] = %d\n", i, shared.buff[i]);
        }
        return(NULL);
}

资料介绍这种轮询消费的方式对cpu是一种浪费;

三、与二相比,变化的有三处,第一是全局变量的定义;第二是生产者的函数;第三是消费者函数;另外main函数与二完全相同,相同的地方就不介绍了;

全局变量的定义

int     nitems;
int     buff[MAXNITEMS];
struct {
  pthread_mutex_t       mutex;
  int                   nput;   /* next index to store */
  int                   nval;   /* next value to store */
} put = { PTHREAD_MUTEX_INITIALIZER };/* 同一、二一样 */

struct {
  pthread_mutex_t       mutex;
  pthread_cond_t        cond;
  int                   nready; /* number ready for consumer */
} nready = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER };
/* 初始化锁mutex为PTHREAD_MUTEX_INITIALIZER,条件变量cond为PTHREAD_COND_INITIALIZER  */

生产者函数

void* produce(void *arg)
{
        for ( ; ; ) {
                Pthread_mutex_lock(&put.mutex);
                if (put.nput >= nitems) {
                        Pthread_mutex_unlock(&put.mutex);
                        return(NULL);           /* array is full, we're done */
                }
                buff[put.nput] = put.nval;
                put.nput++;
                put.nval++;
                Pthread_mutex_unlock(&put.mutex);

                Pthread_mutex_lock(&nready.mutex);
                if (nready.nready == 0)
                        Pthread_cond_signal(&nready.cond);
                nready.nready++;
                Pthread_mutex_unlock(&nready.mutex);

                *((int *) arg) += 1;
        }
}

生产者多了个pthread_cond_signal()函数,作用是唤醒等待正在等待nready.nready 其值变为非零的线程(消费者),需要与关联的互斥锁(nready.mutex)与条件变量(nready.cond)相互协作;

消费者函数

void *
consume(void *arg)
{
        int             i;

        for (i = 0; i < nitems; i++) {
                Pthread_mutex_lock(&nready.mutex);
                while (nready.nready == 0){
                     
                        Pthread_cond_wait(&nready.cond, &nready.mutex);
                        
                }
                nready.nready--;
               
                Pthread_mutex_unlock(&nready.mutex);

                if (buff[i] != i)
                        printf("buff[%d] = %d\n", i, buff[i]);
        }
        return(NULL);
}

消费者多了个pthread_cond_wait()函数,当nready.nready 为零,资料介绍其作用①给nready.mutex解锁;②把消费者消线程投入睡眠,直到另外线程就条件变量nready.cond调用的pthread_cond_signal()函数来唤醒;

以下为演示结果,为了更加直观,我在程序添加了打印信息:

static int num = 50;

生产者线程增加三处打印

Unix网络编程源码解析_进程间通信IPC之生产-消费者问题_第2张图片

在消费者增加四处打印信息

Unix网络编程源码解析_进程间通信IPC之生产-消费者问题_第3张图片

测试结果如下

Unix网络编程源码解析_进程间通信IPC之生产-消费者问题_第4张图片 Unix网络编程源码解析_进程间通信IPC之生产-消费者问题_第5张图片

前面的图是5个线程生产100个条目,后面图是是5个线程生产100w条目(注意关闭消费线程的第一条打印),发现根本没有执行thread_cond_wait()函数;如何让thread_cond_wait()执行呢?

更改主函数中线程启动的顺序,如下

Unix网络编程源码解析_进程间通信IPC之生产-消费者问题_第6张图片

如果只更改顺序还不行(尝试过),最好来点睡眠,调用thread_cond_wait()先让消费者线程睡眠。

测试结果如下:

Unix网络编程源码解析_进程间通信IPC之生产-消费者问题_第7张图片Unix网络编程源码解析_进程间通信IPC之生产-消费者问题_第8张图片

打印-----4--时,消费者线程已经进入睡眠,可以看到两次结果还不一样,我们只要看前面一张,后面一张不用管(可能跟操作系统执行的算法有关。那么执行顺序到底是怎样呢?

重新修改下打印信息的顺序

void produce(void *arg)
{
        for ( ; ; ) {
                Pthread_mutex_lock(&put.mutex);
                if (put.nput >= nitems) {
                        Pthread_mutex_unlock(&put.mutex);
                        return(NULL);           /* array is full, we're done */
                }
                buff[put.nput] = put.nval;
                put.nput++;
                put.nval++;
                Pthread_mutex_unlock(&put.mutex);

                Pthread_mutex_lock(&nready.mutex);
                if (nready.nready == 0){
                        if(num > 0){
                                printf("-----1--\n");
                                num--;
                        }
                        Pthread_cond_signal(&nready.cond);
                        if(num > 0){
                                printf("-----2--\n");
                                num--;
                        }
                }
                nready.nready++;
                if(num > 0){
                        printf("-----3--\n");
                        num--;
                }
                Pthread_mutex_unlock(&nready.mutex);
                if(num > 0){
                        printf("-----4--\n");
                        num--;
                }

                *((int *) arg) += 1;
        }
}
void *consume(void *arg)
{
        int             i;

        for (i = 0; i < nitems; i++) {
                        //printf("buff[%d] = %d\n", i, buff[i]);
                Pthread_mutex_lock(&nready.mutex);
                while (nready.nready == 0){
                        if(num > 0){
                                printf("-----5--\n");
                                num--;
                        }
                        Pthread_cond_wait(&nready.cond, &nready.mutex);
                        if(num > 0){
                                printf("-----6--\n");
                                num--;
                        }
                }
                nready.nready--;
                if(num > 0){
                        printf("-----7--\n");
                        num--;
                }
                Pthread_mutex_unlock(&nready.mutex);
                if(num > 0){
                        printf("-----8--\n");
                        num--;
                }



                if (buff[i] != i)
                        printf("buff[%d] = %d\n", i, buff[i]);
        }
        return(NULL);
}

运行结果如下:

Unix网络编程源码解析_进程间通信IPC之生产-消费者问题_第9张图片

可以看到thread_cond_wait()先释放消费者线程的锁并让该线程睡眠,在thread_cond_wait()返回前,重新上锁,此时while(nready.nready == 0)为假,终止循环;

你可能感兴趣的:(linux)