如果条件为假,线程通常会基于条件变量阻塞,并以原子方式释放等待条件变化的互斥锁。如果另一个线程更改了条件,该线程可能会向相关的条件变量发出信号,从而使一个或多个等待的线程执行以下操作:
■ 唤醒
■ 再次获取互斥锁
■ 重新评估条件
在以下情况下,条件变量可用于在进程之间同步线程:
■ 线程是在可以写入的内存中分配的
■ 内存由协作进程共享
调度策略可确定唤醒阻塞线程的方式。对于缺省值SCHED_OTHER,将按优先级顺序唤醒线程。必须设置和初始化条件变量的属性,然后才能使用条件变量。表4–4列出了用于处理条件变量属性的函数。
表4-5条件变量基本操作
初始化条件变量 pthread_cond_init
基于条件变量阻塞 pthread_cond_wait
解除阻塞特定线程 pthread_cond_signal
在指定的时间之前阻塞 pthread_cond_timedwait
在指定的时间间隔内阻塞 pthread_cond_reltimedwait_np
解除阻塞所有线程 pthread_cond_broadcast
销毁条件变量状态 pthread_cond_destroy
1、唤醒丢失问题
如果线程未持有与条件相关联的互斥锁,则调用pthread_cond_signal()或pthread_cond_broadcast()会产生唤醒丢失错误。满足以下所有条件时,即会出现唤醒丢失问题:
■ 一个线程调用pthread_cond_signal()或pthread_cond_broadcast()
■ 另一个线程已经测试了该条件,但是尚未调用pthread_cond_wait()
■ 没有正在等待的线程
信号不起作用,因此将会丢失仅当修改所测试的条件但未持有与之相关联的互斥锁时,才会出现此问题。只要仅在持有关联的互斥锁同时修改所测试的条件,即可调用pthread_cond_signal()和pthread_cond_broadcast(),而无论这些函数是否持有关联的互斥锁。
2、生成方和使用者问题
并发编程中收集了许多标准的众所周知的问题,生成方和使用者问题只是其中的一个问题。此问题涉及到一个大小限定的缓冲区和两类线程(生成方和使用者),生成方将项放入缓冲区中,然后使用者从缓冲区中取走项。生成方必须在缓冲区中有可用空间之后才能向其中放置内容。使用者必须在生成方向缓冲区中写入之后才能从中提取内容。
条件变量表示一个等待某个条件获得信号的线程队列。
示例4–11中包含两个此类队列。一个队列(less)针对生成方,用于等待缓冲区中出现空位置。另一个队列(more)针对使用者,用于等待从缓冲槽位的空位置中提取其中包含的信息。该示例中还包含一个互斥锁,因为描述该缓冲区的数据结构一次只能由一个线程访问。
示例4–11生成方和使用者的条件变量问题
1
2 3 4 5 6 7 8 9 10 11 |
typedef
struct
{ char buf[BSIZE]; //缓冲区 int occupied; int nextin,nextout; //读写位置 pthread_mutex_t mutex; //互斥锁 pthread_cond_t more; //非空 pthread_cond_t less; //空 } buffer_t; buffer_t buffer; |
如示例4–12中所示,生成方线程获取该互斥锁以保护buffer数据结构,然后,缓冲区确定是否有空间可用于存放所生成的项。如果没有可用空间,生成方线程会调用pthread_cond_wait()。pthread_cond_wait()会导致生成方线程连接正在等待less条件获得信号的线程队列。less表示缓冲区中的可用空间。
与此同时,在调用pthread_cond_wait()的过程中,该线程会释放互斥锁的锁定。正在等待的生成方线程依赖于使用者线程在条件为真时发出信号,如示例4–12中所示。该条件获得信号时,将会唤醒等待less的第一个线程。但是,该线程必须再次锁定互斥锁,然后才能从pthread_cond_wait()返回。获取互斥锁可确保该线程再次以独占方式访问缓冲区的数据结构。该线程随后必须检查缓冲区中是否确实存在可用空间。如果空间可用,该线程会向下一个可用的空位置中进行写入。
与此同时,使用者线程可能正在等待项出现在缓冲区中。这些线程正在等待条件变量more。刚在缓冲区中存储内容的生成方线程会调用pthread_cond_signal()以唤醒下一个正在等待的使用者。如果没有正在等待的使用者,此调用将不起作用。最后,生成方线程会解除锁定互斥锁,从而允许其他线程处理缓冲区的数据结构.
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 |
void producer(buffer_t *b,
char item)
{ pthread_mutex_lock(&b->mutex); while (b->occupied >= BSIZE) pthread_cond_wait(&b->less, &b->mutex); assert(b->occupied < BSIZE); b->buf[b->nextin++] = item; b->nextin %= BSIZE; b->occupied++; /* now: either b->occupied < BSIZE and b->nextin is the index of the next empty slot in the buffer, or b->occupied == BSIZE and b->nextin is the index of the next (occupied) slot that will be emptied by a consumer (such as b->nextin == b->nextout) */ pthread_cond_signal(&b->more); pthread_mutex_unlock(&b->mutex); } char consumer(buffer_t *b) { char item; pthread_mutex_lock(&b->mutex); while(b->occupied <= 0) pthread_cond_wait(&b->more, &b->mutex); assert(b->occupied > 0); item = b->buf[b->nextout++]; b->nextout %= BSIZE; b->occupied--; /* now: either b->occupied > 0 and b->nextout is the index of the next occupied slot in the buffer, or b->occupied == 0 and b->nextout is the index of the next (empty) slot that will be filled by a producer (such as b->nextout == b->nextin) */ pthread_cond_signal(&b->less); pthread_mutex_unlock(&b->mutex); return(item); } |
下面是书本上一个实际的生产者和消费的真实代码如下:
此程序用来处理生产消费问题,整个临时存储空间为2,即在任意时刻,最多能够有2个产品存放在临时空间,如果已经有2个产品存放在临时空间,将阻塞生产线程。同理,如果临时空间没有产品,显示需要阻塞消费线程。此程序中主要实现了生产和消费两个线程的同步。
1、锁定互斥。
2、测试条件是否满足。
3、如果满足,执行操作,完成解锁互斥锁。
4、如果第二步不满足,使用条件变量机制等待,当另一个线程使此条件满足时,执行第三步。
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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
#include <stdio.h>
#include <stdlib.h> #include <unistd.h> #include <time.h> #include "pthread.h" #define BUFFER_SIZE 2 /* Circular buffer of integers. */ struct prodcons { int buffer[BUFFER_SIZE]; /* the actual data */ pthread_mutex_t lock; /* mutex ensuring exclusive access to buffer */ int readpos, writepos; /* positions for reading and writing */ pthread_cond_t notempty; /* signaled when buffer is not empty */ pthread_cond_t notfull; /* signaled when buffer is not full */ }; /* Initialize a buffer */ void init( struct prodcons *prod) { pthread_mutex_init(&prod->lock, NULL); pthread_cond_init(&prod->notempty, NULL); pthread_cond_init(&prod->notfull, NULL); prod->readpos = 0; prod->writepos = 0; } /* Store an integer in the buffer */ void put( struct prodcons *prod, int data) { pthread_mutex_lock(&prod->lock); /* Wait until buffer is not full */ while ((prod->writepos + 1) % BUFFER_SIZE == prod->readpos) { printf( "producer wait for not full\n"); pthread_cond_wait(&prod->notfull, &prod->lock); } /* Write the data and advance write pointer */ prod->buffer[prod->writepos] = data; prod->writepos++; if (prod->writepos >= BUFFER_SIZE) prod->writepos = 0; /*Signal that the buffer is now not empty */ pthread_cond_signal(&prod->notempty); pthread_mutex_unlock(&prod->lock); } /* Read and remove an integer from the buffer */ int get( struct prodcons *prod) { int data; pthread_mutex_lock(&prod->lock); /* Wait until buffer is not empty */ while (prod->writepos == prod->readpos) { printf( "consumer wait for not empty\n"); pthread_cond_wait(&prod->notempty, &prod->lock); } /* Read the data and advance read pointer */ data = prod->buffer[prod->readpos]; prod->readpos++; if (prod->readpos >= BUFFER_SIZE) prod->readpos = 0; /* Signal that the buffer is now not full */ pthread_cond_signal(&prod->notfull); pthread_mutex_unlock(&prod->lock); return data; } #define OVER (- 1) struct prodcons buffer; /*--------------------------------------------------------*/ void *producer( void *data) { int n; for (n = 0; n < 5; n++) { printf( "producer sleep 1 second......\n"); sleep( 1); printf( "put the %d product\n", n); put(&buffer, n); } for(n = 5; n < 10; n++) { printf( "producer sleep 3 second......\n"); sleep( 3); printf( "put the %d product\n", n); put(&buffer, n); } put(&buffer, OVER); printf( "producer stopped!\n"); return NULL; } /*--------------------------------------------------------*/ void *consumer( void *data) { int d = 0; while ( 1) { printf( "consumer sleep 2 second......\n"); sleep( 2); d = get(&buffer); printf( "get the %d product\n", d); // d = get(&buffer); if (d == OVER ) break; } printf( "consumer stopped!\n"); return NULL; } /*--------------------------------------------------------*/ int main( int argc, char *argv[]) { pthread_t th_a, th_b; void *retval; init(&buffer); pthread_create(&th_a, NULL, producer, 0); pthread_create(&th_b, NULL, consumer, 0); /* Wait until producer and consumer finish. */ pthread_join(th_a, &retval); pthread_join(th_b, &retval); return 0; } |