3.线程的同步
POSIX提供了两个同步的原语,mutex(互斥)和condition(条件)变量。互斥是可以被用来控制共享变量的访问简单的锁原语。注意,对于线程来说,整个地址空间都是共享的,所以所有的东西都可以被当作共享资源。然而,在大多数情况下,线程使用私有的本地变量(在pthread_create及连续的函数中制造出来的)单独的工作(理论上),并通过全局变量来把它们的成果合并起来。对于线程都要进行写操作的变量的访问必须被控制。
让我们创建一个readers/writers程序,在这个程序中有一个reader和一个writer通过一个共享的缓存来通信并且通过互斥来控制访问:
void reader_function(void);
void writer_function(void);
char buffer;
int buffer_has_item = 0;
pthread_mutex_t mutex;
struct timespec delay;
main()
{
pthread_t reader;
delay.tv_sec = 2;
delay.tv_nsec = 0;
pthread_mutex_init(&mutex, pthread_mutexattr_default);
pthread_create( &reader, pthread_attr_default, (void*)&reader_function,
NULL);
writer_function();
}
void writer_function(void)
{
while(1)
{
pthread_mutex_lock( &mutex );
if ( buffer_has_item == 0 )
{
buffer = make_new_item();
buffer_has_item = 1;
}
pthread_mutex_unlock( &mutex );
pthread_delay_np( &delay );
}
}
void reader_function(void)
{
while(1)
{
pthread_mutex_lock( &mutex );
if ( buffer_has_item == 1)
{
consume_item( buffer );
buffer_has_item = 0;
}
pthread_mutex_unlock( &mutex );
pthread_delay_np( &delay );
}
}
在这个简单的程序中我们假设缓存只能容纳一项,所以它一直是处于两种状态的其中一种,有内容或没有。writer首先锁住互斥变量,如果已经被上锁了,那么该线程阻塞直到被解锁,然后查看缓存是否为空。如果缓存为空,它创建一个新的项并设置标记buffer_has_item,因此reader会知道现在缓存里有内容。然后解锁互斥变量并延时2秒钟让reader有机会消耗这项内容。这个延时与我们的前一个延时不同,它是用来改善程序性能的。如果没有这个延时,writer在释放锁之后可能马上又获得了锁并试图再制造另一项内容。reader很可能没有机会这么快的消耗这项内容,所以延时是一个好办法。
reader的情况也差不多。他获得这个锁,查看是否存在内容,如果有就消耗它。它释放锁并延时一小段时间来给writer机会去制造新的内容。在这个例子中reader和writer会一直执行下去,制造和消耗内容。如果一个互斥变量不再被需要,可以通过pthread_mutex_destroy(&mutex)来释放。观察在互斥变量的初始化函数中,我们使用被要求的pthread_ mutexattr_default作为互斥变量的属性。在OSF/1中,互斥变量属性没什么作用,所以强烈推荐使用默认值。
适当的使用户斥变量保证消除了竞争情形。但是,互斥变量本身十分的弱,因为它只有两个状态:被锁和未被锁。POSIX的条件变量通过允许一个线程阻塞而去等待另一个线程的信号来补充互斥变量。当这个信号被接受到了,被阻塞的信号被唤醒去尝试获得一个与之像关的互斥变量。因此信号和互斥可以被合并起来消除readers/writers带来的自旋锁问题。我们已经设计了一个通过pthreads的mutex和condition实现的简单的整数信号量并且今后会在那个环境中讨论同步的问题。信号量的代码可以在附录A中找到,关于条件变量的细节问题
可以在帮助(man)页中找到。
本节提到的函数:
pthread_mutex_init(), pthread_mutex_lock(),
pthread_mutex_unlock(), and pthread_mutex_destroy().