1. 程式人生 > >linux中mutex和semaphore的區別

linux中mutex和semaphore的區別

很多程式設計的書裡在介紹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則有順序的保證,使得每個使用者都能依次獲得他,但是相應的會損失一點效率。