可以使用 pthread 的互斥接口来保护数据,确保同一时间只有一个线程访问数据。互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变成可运行状态,第一个变为运行的线程就可以对互斥量加锁,其他线程就会看到互斥量依然是锁着的,只能回去再次等待它重新变为可用。在这种方式下,每次只有一个线程可以向前执行。
只有将所有线程都设计成遵守相同数据访问规则的,互斥机制才能正常工作。操作系统并不会为我们做数据访问的串行化。如果允许其中的某个线程在没有得到锁的情况下也可以访问共享资源,那么即使其他的线程在使用共享资源前都申请锁,也还是会出现数据不一致的问题。
互斥变量是用pthread_mutex_t
数据类型表示的。在使用互斥变量以前,必须首先对它进行初始化,可以把它设置为常量PTHREAD_MUTEX_INITIALIZER
(只适用于静态分配的互斥量),也可以通过调用pthread_mutex_init
函数进行初始化。如果动态分配互斥量(例如,通过调用malloc
函数),在释放内存前需要调用pthread_mutex_destroy
。
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
要用默认的属性初始化互斥量,只需把attr设为NULL
。我们将在12.4节中讨论互斥量属性。
对互斥量进行加锁,需要调用 pthread_mutex_lock
。如果互斥量已经上锁,调用线程将阻塞直到互斥量被解锁。对互斥量解锁,需要调用pthread_mutex_unlock
。
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//所有函数的返回值:若成功,返回0;否则,返回错误编号
如果线程不希望被阻塞,它可以使用pthread_mutex_trylock
尝试对互斥量进行加锁。如果调用 pthread_mutex_trylock
时互斥量处于未锁住状态,那么 pthread_mutex_trylock
将锁住互斥量,不会出现阻塞直接返回0,否则pthread_mutex_trylock
就会失败,不能锁住互斥量,返回EBUSY
。
实例
图11-10描述了用于保护某个数据结构的互斥量。当一个以上的线程需要访问动态分配的对象时,我们可以在对象中嵌入引用计数,确保在所有使用该对象的线程完成数据访问之前,该对象内存空间不会被释放。
在对引用计数加 1、减 1、检查引用计数是否到达 0 这些操作之前需要锁住互斥量。在foo_alloc
函数中将引用计数初始化为 1 时没必要加锁,因为在这个操作之前分配线程是唯一引用该对象的线程。但是在这之后如果要将该对象放到一个列表中,那么它就有可能被别的线程发现,这时候需要首先对它加锁。
在使用该对象前,线程需要调用foo_hold
对这个对象的引用计数加1。当对象使用完毕时,必须调用foo_rele
释放引用。最后一个引用被释放时,对象所占的内存空间就被释放。
在这个例子中,我们忽略了线程在调用foo_hold
之前是如何找到对象的。如果有另一个线程在调用foo_hold
时阻塞等待互斥锁,这时即使该对象引用计数为0,foo_rele
释放该对象的内存仍然是不对的。可以通过确保对象在释放内存前不会被找到这种方式来避免上述问题。可以通过下面的例子来看看如何做到这一点。
#include <stdlib.h>
#include <pthread.h>
struct foo {
int f_count;
pthread_mutex_t f_lock;
int f_id;
/* ... more stuff here ... */
};
struct foo *
foo_alloc(int id) /* allocate the object */
{
struct foo *fp;
if ((fp = malloc(sizeof(struct foo))) != NULL) {
fp->f_count = 1;
fp->f_id = id;
if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
free(fp);
return(NULL);
}
/* ... continue initialization ... */
}
return(fp);
}
void
foo_hold(struct foo *fp) /* add a reference to the object */
{
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
void
foo_rele(struct foo *fp) /* release a reference to the object */
{
pthread_mutex_lock(&fp->f_lock);
if (--fp->f_count == 0) { /* last reference */
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
} else {
pthread_mutex_unlock(&fp->f_lock);
}
}
图11-10 使用互斥量保护数据结构