互斥量(也称为互斥锁)出自POSIX线程标准,可以用来同步同一进程中的各个线程。当然如果一个互斥量存放在多个进程共享的某个内存区中,那么还可以通过互斥量来进行进程间的同步。
互斥量,从字面上就可以知道是相互排斥的意思,它是最基本的同步工具,用于保护临界区(共享资源),以保证在任何时刻只有一个线程能够访问共享的资源。
互斥量类型声明为pthread_mutex_t数据类型,在<bits/pthreadtypes.h>中有具体的定义。
/* Initialize a mutex. */ int pthread_mutex_init (pthread_mutex_t *__mutex,\ __const pthread_mutexattr_t *__mutexattr); /* Destroy a mutex. */ int pthread_mutex_destroy (pthread_mutex_t *__mutex);
上面两个函数分别由于互斥量的初始化和销毁。
如果互斥量是静态分配的,可以通过常量进行初始化,如下:
pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER;
当然也可以通过pthread_mutex_init()进行初始化。对于动态分配的互斥量由于不能直接赋值进行初始化就只能采用这种方式进行初始化,pthread_mutex_init()的第二个参数是互斥量的属性,如果采用默认的属性设置,可以传入NULL。
当不在需要使用互斥量时,需要调用pthread_mutex_destroy()销毁互斥量所占用的资源。
/* 初始化互斥量属性对象 */ int pthread_mutexattr_init (pthread_mutexattr_t *__attr); /* 销毁互斥量属性对象 */ int pthread_mutexattr_destroy (pthread_mutexattr_t *__attr); /* 获取互斥量属性对象在进程间共享与否的标志 */ int pthread_mutexattr_getpshared (__const pthread_mutexattr_t *__restrict __attr, \ int *__restrict __pshared); /* 设置互斥量属性对象,标识在进程间共享与否 */ int pthread_mutexattr_setpshared (pthread_mutexattr_t *__attr, int __pshared);
互斥量在初始化的时候pthread_mutex_init的第二个参数是互斥量的属性,如果为NULL空指针,那么就使用默认属性。
互斥量属性的数据类型为pthread_mutexattr_t,它的初始化和销毁和互斥量类似。一旦互斥量属性对象被初始化后,就可以通过调用不同的函数启用或禁止特定的属性。这里列出了一个设置特定属性的函数:pthread_mutexattr_setpshared,可以用于指定互斥量在不同进程间共享,这样可以通过互斥量来同步不同的进程,当然前提是该互斥量位于进程间的共享内存区。
pthread_mutexattr_setpshared()函数的第二个参数__pshared用于设定是否进程间共享,其值可以是PTHREAD_PROCESS_PRIVATE或PTHREAD_PROCESS_SHARED,后者是设置进程间共享。
下面是使互斥量可以在进程间共享的大致过程:
pthread_mutex_t *pSharedMutex; //指向共享内存区的互斥量 pthread_mutexattr_t mutexAttr; //互斥量属性 pSharedMutex = /*一个指向共享内存区的指针*/; pthread_mutexattr_init(&mutexAttr); pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_SHARED); pthread_mutex_init(pSharedMutex, &mutexAttr);
/* Try locking a mutex. */ int pthread_mutex_trylock (pthread_mutex_t *__mutex); /* Lock a mutex. */ int pthread_mutex_lock (pthread_mutex_t *__mutex); /* Unlock a mutex. */ int pthread_mutex_unlock (pthread_mutex_t *__mutex);
这几个函数都很简单,通过pthread_mutex_lock()函数获得访问共享资源的权限,如果已经有其他线程锁住互斥量,那么该函数会是线程阻塞指定该互斥量解锁为止。 pthread_mutex_trylock()是对应的非阻塞函数,如果互斥量已被占用,它会返回一个EBUSY错误。访问完共享资源后,一定要通过pthread_mutex_unlock() 函数,释放占用的互斥量。允许其他线程访问该资源。
这里要强调的是:互斥量是用于上锁的,不能用于等待。
简单说就是,互斥量的使用流程应该是:线程占用互斥量,然后访问共享资源,最后释放互斥量。而不应该是:线程占用互斥量,然后判断资源是否可用,如果不可用,释放互斥量,然后重复上述过程。这种行为称为轮转或轮询,是一种浪费CPU时间的行为。
下面是一个测试代码,模拟同步问题中经典的生产者消费者问题。
#include <iostream> #include <queue> #include <cstdlib> #include <unistd.h> #include <pthread.h> using namespace std; pthread_mutex_t mutex; queue<int> product; void * produce(void *ptr) { for (int i = 0; i < 10; ++i) { pthread_mutex_lock(&mutex); product.push(i); pthread_mutex_unlock(&mutex); //sleep(1); } } void * consume(void *ptr) { for (int i = 0; i < 10;) { pthread_mutex_lock(&mutex); if (product.empty()) { pthread_mutex_unlock(&mutex); continue; } ++i; cout<<"consume:"<<product.front()<<endl; product.pop(); pthread_mutex_unlock(&mutex); //sleep(1); } } int main() { pthread_mutex_init(&mutex, NULL); pthread_t tid1, tid2; pthread_create(&tid1, NULL, consume, NULL); pthread_create(&tid2, NULL, produce, NULL); void *retVal; pthread_join(tid1, &retVal); pthread_join(tid2, &retVal); return 0; } 运行结果如下: consume:0 consume:1 consume:2 consume:3 consume:4 consume:5 consume:6 consume:7 consume:8 consume:9上述代码, consume 在判断队列中是否有数据的时候就是通过轮询的方式进行的,这种行为很浪费 CPU 资源,可以通过条件变量来解决这个问题。