红联Linux门户
Linux帮助

linux中mutex和semaphore的区别

发布时间:2016-06-05 15:24:49来源:linux网站作者:潘振杰

很多编程的书里在介绍mutex和semaphore的时候都会说,mutex是一种特殊的semaphore.

当semaphore的N=1时,就变成了binary semaphore,也就等同与mutex了。

但是实际上,在linux中,他们的实现什有区别的,导致最后应用的行为也是有区别的。


先看下面这个例子,这是一段linux kernel的代码:

#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/mutex.h> 
#include <linux/semaphore.h> 
#include <linux/sched.h> 
#include <linux/delay.h> 

static DEFINE_MUTEX(g_mutex); 
static DEFINE_SEMAPHORE(g_semaphore); 

static int fun1(void *p) 

while (true) { 
mutex_lock(&g_mutex); 
msleep(1000); 
printk("1\n"); 
mutex_unlock(&g_mutex); 

return 0; 

static int fun2(void *p) 

while (true) { 
mutex_lock(&g_mutex); 
msleep(1000); 
printk("2\n"); 
mutex_unlock(&g_mutex); 

return 0; 

static int fun3(void *p) 

while (true) { 
down(&g_semaphore); 
msleep(1000); 
printk("3\n"); 
up(&g_semaphore); 

return 0; 

static int fun4(void *p) 

while (true) { 
down(&g_semaphore); 
msleep(1000); 
printk("4\n"); 
up(&g_semaphore); 

return 0; 

static int hello_init(void) 

kernel_thread(fun1, NULL, 0); 
kernel_thread(fun2, NULL, 0); 
kernel_thread(fun3, NULL, 0); 
kernel_thread(fun4, NULL, 0); 
return 0; 

module_init(hello_init); 


这段代码很简单,4个线程,2个去获取mutex,2个去获取semaphore。

我们可以先只enable thread1和thread2。

我开始预期的结果什输出1212121212...

但是实际的结果是111111111...22222222222...11111111111...

假设enable thread3和thread4,输出则变成了34343434343434...

显然,mutex和semaphore使用的结果不一样。


为什么会造成这样的结果哪?

我们分析一下kernel里mutex和semaphore的实现就可以明白了

kernel/mutex.c

__mutex_unlock_common_slowpath(atomic_t *lock_count, int nested) 

struct mutex *lock = container_of(lock_count, struct mutex, count); 
unsigned long flags; 

spin_lock_mutex(&lock->wait_lock, flags); 
mutex_release(&lock->dep_map, nested, _RET_IP_); 
debug_mutex_unlock(lock); 

/*
* some architectures leave the lock unlocked in the fastpath failure
* case, others need to leave it locked. In the later case we have to
* unlock it here
*/ 
if (__mutex_slowpath_needs_to_unlock()) 
atomic_set(&lock->count, 1); 

if (!list_empty(&lock->wait_list)) { 
/* get the first entry from the wait-list: */ 
struct mutex_waiter *waiter = 
list_entry(lock->wait_list.next, 
struct mutex_waiter, list); 

debug_mutex_wake_waiter(lock, waiter); 

wake_up_process(waiter->task); 

spin_unlock_mutex(&lock->wait_lock, flags); 


在解锁的时候,会先把lock->count设置成1,

然后从等待这个mutex的队列里取出第一个任务,并wake_up这个任务。

这里要注意,wake_up_process只是把这个任务设置成可调度,并不是直接就进行调度了。

所以当一个线程unlock mutex之后,只要在自己还没有被调度出去之前再次很快的lock mutex的话,他依旧会成功。

于是,这就出现了一开始那个程序的结果。

在代码本身没有死锁的情况下,不合适得使用mutex,会造成饥饿的发生。


那semaphore到底是如何避免这样的情况发生的哪?

kernel/semaphore.c

void down(struct semaphore *sem) 

unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags); 
if (likely(sem->count > 0)) 
sem->count--; 
else 
__down(sem); 
raw_spin_unlock_irqrestore(&sem->lock, flags); 

static inline int __sched __down_common(struct semaphore *sem, long state, 
long timeout) 

struct task_struct *task = current; 
struct semaphore_waiter waiter; 

list_add_tail(&waiter.list, &sem->wait_list); 
waiter.task = task; 
waiter.up = false; 

for (;;) { 
if (signal_pending_state(state, task)) 
goto interrupted; 
if (unlikely(timeout <= 0)) 
goto timed_out; 
__set_task_state(task, state); 
raw_spin_unlock_irq(&sem->lock); 
timeout = schedule_timeout(timeout); 
raw_spin_lock_irq(&sem->lock); 
if (waiter.up) 
return 0; 

timed_out: 
list_del(&waiter.list); 
return -ETIME; 
interrupted: 
list_del(&waiter.list); 
return -EINTR; 

 
void up(struct semaphore *sem) 

unsigned long flags; 

raw_spin_lock_irqsave(&sem->lock, flags); 
if (likely(list_empty(&sem->wait_list))) 
sem->count++; 
else 
__up(sem); 
raw_spin_unlock_irqrestore(&sem->lock, flags); 

static noinline void __sched __up(struct semaphore *sem) 

struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list, 
struct semaphore_waiter, list); 
list_del(&waiter->list); 
waiter->up = true; 
wake_up_process(waiter->task); 


首先在down的时候,回去查看sem->count的值,假如大于0,就进行--操作。

这就好比第一个线程去down semaphore。

等到第二个线程再去down的时候,count为0了,就进入了__down_common函数。

这个函数里面会一直检查waiter.up,知道为true了才会退出。

至此,第二个线程就被block在了down函数里。


等到第一个线程up semaphore,假如这个semaphore的等待队列里还有任务,则设置waiter->up为true并唤醒任务。

这里用的也是wake_up_process,貌似和mutex的实现什一样的,但是接下来就不一样了。

假设第一个线程之后又很快的去down semaphore,会发生什么哪?

由于sem->count还是为0,这个线程在down的时候就会被block住而发生调度。

第二个线程此时就可以获得semaphore而继续执行代码了。

一直直到没有任何线程在等待队列里了,sem->count才会被++。

所以,semaphore就变成了这样的行为。


总结:mutex在使用时没有任何顺序的保证,它仅仅是保护了资源,但是效率会比较高。

而semaphore则有顺序的保证,使得每个使用者都能依次获得他,但是相应的会损失一点效率。


本文永久更新地址:http://www.linuxdiyf.com/linux/21258.html