共计 5007 个字符,预计需要花费 13 分钟才能阅读完成。
互斥量–Mutex
互斥量同步是多线程同步 的一种访问手段互斥量的作用犹如给某个对象加上一把锁,每次只允许一个线程去访问,其他线程的话处于等待状态。假如你对代码的关键部分进行访问控制的话,可以在进入代码段之前加上一把锁,锁定一个互斥量,完成操作之后在解开。
互斥量相关函数分析
互斥锁的创建
有两种方法创建互斥锁,静态方式和动态方式 。
静态方式:POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t定义如下:
typedef union
{
struct __pthread_mutex_s
{
int __lock;
unsigned int __count;
int __owner;
/* KIND must stay at this position in the structure to maintain
binary compatibility. */
int __kind;
unsigned int __nusers;
__extension__ union
{
int __spins;
__pthread_slist_t __list;
};
} __data;
char __size[__SIZEOF_PTHREAD_MUTEX_T];
long int __align;
} pthread_mutex_t;
在 LinuxThreads 实现中,pthread_mutex_t 是一个结构, PTHREAD_MUTEX_INITIALIZER而则是一个结构常量。
动态方式:采用 pthread_mutex_init()函数来初始化互斥锁,API 定义如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
第一个参数是指向互斥量的数据结构指针,第二个参数是定义互斥量属性的指向pthread_mutexattr_t的指针。
互斥锁属性
互斥锁的属性在创建锁的时候指定,在 LinuxThreads 实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。
有四个值可供选择:
(1)PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
(2)PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次 unlock 解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
(3)PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与 PTHREAD_MUTEX_TIMED_NP 类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
(4)PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
上锁与解锁操作
锁操作主要包括加锁 pthread_mutex_lock() 、解 锁 pthread_mutex_unlock() 和测试加锁pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁类型, 解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回 EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并 没有这种限制,这个不同目前还没有得到解释。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。
函数定义如下:
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
pthread_mutex_trylock()语义与 pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY 而不是挂起等待。
注意:互斥量只有两个状态即上锁与未上锁状态。一个互斥量不能同时被不同的线程锁住。如果一个线程试图对一个已经被加锁的互斥量加锁则会被阻塞直到该互斥量被解锁。
下面给出多线程面试经典例子消费者与生产者问题:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#define NUMS 10 //表示生产,消费的次数
#define CAPACITY 5 //定义缓冲区最大值
int capacity = 0; //当前缓冲区的产品个数
pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;//互斥量
void *produce(void *args)
{
int i = 0;
for (; i < NUMS; )
{
pthread_mutex_lock(&mylock);//加锁
if (capacity >= CAPACITY) //当前产品个数大于等于缓冲区最大值,则不把产品放入缓冲区。
{
printf("缓冲区已满,无法放入产品\n");
} else {//将产品放入缓冲区
++capacity;
printf("生产者存入一个产品, 缓冲区大小为:%d\n", capacity);
i++;
}
pthread_mutex_unlock(&mylock);
}
return ((void *) 0);
}
void * consume(void *args)
{
int i = 0;
for (; i < NUMS; )
{
pthread_mutex_lock(&mylock);
if (capacity > 0)
{
--capacity;
printf("消费者消耗一个产品,缓冲区大小为:%d\n", capacity);
i++;
} else
{
printf("缓冲区已空,无法消耗产品\n");
}
pthread_mutex_unlock(&mylock);
}
return ((void *) 0);
}
int main(int argc, char** argv) {
int err;
pthread_t produce_tid, consume_tid;
void *ret;
err = pthread_create(&produce_tid, NULL, produce, NULL);//创建线程
if (err != 0)
{
printf("线程创建失败:%s\n", strerror(err));
exit(-1);
}
err = pthread_create(&consume_tid, NULL, consume, NULL);
if (err != 0)
{
printf("线程创建失败:%s\n", strerror(err));
exit(-1);
}
err = pthread_join(produce_tid, &ret);//主线程等到子线程退出
if (err != 0)
{
printf("生产着线程分解失败:%s\n", strerror(err));
exit(-1);
}
err = pthread_join(consume_tid, &ret);
if (err != 0)
{
printf("消费者线程分解失败:%s\n", strerror(err));
exit(-1);
}
return (EXIT_SUCCESS);
}
部分运行结果如下图:
![pic](http://7xifjo.com1.z0.glb.clouddn.com/20140820104502484.png)
读者会发现不是对线程进行加锁了?为什么还是会出现这种线程不同步的问题?你会发现我们只用了一个互斥量,程序中出现两个线程,两个线程只对一个互斥量进行上锁还有解锁,当其中一个线程从出现解锁的时候,另外一个线程还有刚才线程都会重新尝试加锁,加锁成功的线程会继续运行,由图中的运行结果看在生产者放满了产品后,只提示缓冲区已满一次,你要知道生产者线程函数中for循环是可以循环10次的,由此可见在一次提示缓冲区已满之后,互斥量就被消费者加锁成功这样就会运行消费者线程函数,这样下面运行结果出现的缓冲区已空出现多次也是可以解释清楚的!
解决办法如下,一个互斥量没法解决,可以使用两个互斥量,生产者线程函数与消费者线程函数各自加锁一个互斥量这样互补干扰。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#define NUMS 10 //表示生产,消费的次数
#define CAPACITY 5 //定义缓冲区最大值
int capacity = 0; //当前缓冲区的产品个数
pthread_mutex_t mylockA=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mylockB=PTHREAD_MUTEX_INITIALIZER;
void *produce(void *args)
{
int i = 0,err;
for (; i < NUMS; )
{
pthread_mutex_lock(&mylockA);//加锁
if (capacity >= CAPACITY) //当前产品个数大于等于缓冲区最大值,则不把产品放入缓冲区。
{
printf("缓冲区已满,无法放入产品\n");
} else {//将产品放入缓冲区
++capacity;
printf("生产者存入一个产品, 产品个数:%d\n", capacity);
i++;
}
err=pthread_mutex_unlock(&mylockB);
}
return ((void *) 0);
}
void * consume(void *args)
{
int i = 0;
for (; i < NUMS; )
{
pthread_mutex_lock(&mylockB);
if (capacity > 0)
{
--capacity;
printf("消费者消耗一个产品,产品个数:%d\n", capacity);
i++;
} else
{
printf("缓冲区已空,无法消耗产品\n");
}
pthread_mutex_unlock(&mylockA);
}
return ((void *) 0);
}
int main(int argc, char** argv) {
int err;
pthread_t produce_tid, consume_tid;
void *ret;
if(capacity==0)
pthread_mutex_lock(&mylockB);
else
if(capacity==CAPACITY)
pthread_mutex_lock(&mylockA);
err = pthread_create(&produce_tid, NULL, produce, NULL);//创建线程
if (err != 0)
{
printf("线程创建失败:%s\n", strerror(err));
exit(-1);
}
err = pthread_create(&consume_tid, NULL, consume, NULL);
if (err != 0)
{
printf("线程创建失败:%s\n", strerror(err));
exit(-1);
}
err = pthread_join(produce_tid, &ret);//主线程等到子线程退出
if (err != 0)
{
printf("生产着线程分解失败:%s\n", strerror(err));
exit(-1);
}
err = pthread_join(consume_tid, &ret);
if (err != 0)
{
printf("消费者线程分解失败:%s\n", strerror(err));
exit(-1);
}
return (EXIT_SUCCESS);
}
运行结果如下图:
结论
使用互斥量就是利用线程对互斥量加锁然后运行本线程函数,这样另外一个线程尝试加锁互斥量就会被阻塞直到互斥量解锁。合理使用互斥量可以实现多线程的同步,下一篇将会介绍条件变量与互斥量同时使用的情形。