Linux互斥鎖機制

  一、深入理解linux互斥鎖(mutex)

  鎖機制,可以說是linux整個系統的精髓所在,linux內核都是圍繞着同步在運轉。在多進程和多線程編程中,鎖起着極其重要的作用。我這裏說的是互斥鎖,其實是泛指linux中所有的鎖機制。我在這裏不講如果創建鎖,關於鎖的創建,網上代碼很多,我在這裏就不多說了。我要談一談一個讓所有剛剛接觸鎖機制的程序員都很困惑的問題:如何使用以及鎖機制在程序中是如何運作的。

  1、爲什麼要使用鎖

這個就比較簡單,linux裏面,鎖的種類很多,包括互斥鎖,文件鎖,讀寫鎖······其實信號量說白了也是一種鎖。使用鎖的目的是達到同步的作用,使共享資源在同一時間內,只有能有一個進程或者線程對他進行操作。

  2、linux是如何通過鎖來實現對數據的保護和維護的

這個問題是我要將的重點。很多剛剛接觸鎖機制的程序員,都會犯這種錯誤。比如,此時有2個線程,分別是線程A,線程B。A和B共享了資源M。爲了同步A和B,使得同一時刻,同意時刻,只有一個線程對M操作。於是,很自然的會在A中對M資源先lock,等到A對M操作完畢之後,然後做一個操作unlock。B中則因爲A加了鎖,B就直接操作M。這個時候,你會發現,B同樣可以操作到M。這個是爲什麼呢?

  我們利索當然的把檢測鎖的任務交給了操作系統,交給了內核。可以翻看APUE上對於所的講解,其中一部分是這麼寫的:

  This mutual-exclusion mechanism works only if we design our threads to follow the same data-access rules. The operating system doesn't serialize access to data for us. If we allow one thread to access a shared resource without first acquiring a lock, then inconsistencies can occur even though the rest of our threads do acquire the lock before attempting to access the shared resource.

這裏This mutual-exclusion mechanism指的就是鎖機制。說的很清楚,只有程序員設計線程的時候,都遵循同一種數據訪問規則,鎖機制纔會起作用。操作系統不會爲我們序列化數據訪問,也就是說,操作系統不會爲我們擬定任何數據訪問順序,到底是A在先還是B在先,操作系統不會爲我們規定。如果我們允許一個線程在沒有多的鎖(lock)之前,就對共享數據進行訪問操作,那麼,即使我們其他的線程都在訪問之前試圖去先鎖住資源(獲取鎖),同樣會導致數據訪問不一致,即多個線程同時在操作共享資源。

從上面文字可以看出,操作系統不會爲我們去檢查,此時是不是有線程已經把資源鎖住了。爲了使鎖能夠正常工作,爲了保護共享資源,我們只有在設計線程的時候,所有線程都用同一種方法去訪問共享數據,也就是訪問數據之前,務必先獲取鎖,然後再操作,操作完之後要解鎖(unlock)。操作系統提供鎖機制,就是提供了一種所有程序員都必須遵循的規範。而不是說我們鎖住資源,其他線程訪問共享資源的時候,讓操作系統去爲我們檢查數據是否有其他的線程在操作。

 

版權申明:
轉載文章請註明原文出處http://blog.csdn.net/feiyinzilgd/archive/2010/08/16/5816653.aspx


二、 pthread_mutex 家庭成員:

1.int pthread_mutex_init (pthread_mutex_t *mutex , pthread_mutexattr_t * attr );

Description

The pthread_mutex_init function initializes the given mutex with the given attributes. Ifattr is null, then the default attributes are used. 


2. int pthread_mutex_destroy (pthread_mutex_t *mutex );

Description

The pthread_mutex_destroy function destroys the given mutex. If the mutex is already destroyed, thenerrno is set to EINVAL. If the mutex is locked, then errno is set to EBUSY


3.int pthread_mutex_lock (pthread_mutex_t *mutex );

Description

The pthread_mutex_lock function locks the given mutex. If the mutex is already locked, then the calling thread blocks until the thread that currently holds the mutex unlocks it.


4.int pthread_mutex_trylock (pthread_mutex_t *mutex );

Description

The pthread_mutex_trylock function tries to lock the given mutex. If the mutex is already locked, the function returns without waiting for the mutex to be unlocked.

Returns

The pthread_mutex_trylock function returns zero if the call is successful, otherwise it setserrno to EINVAL and returns -1


5.int pthread_mutex_unlock (pthread_mutex_t *mutex );

Description

The pthread_mutex_unlock function unlocks the given mutex.



三、成員作用

1.  互斥鎖的初始化和銷燬

.初始化有兩種初始化方式:

a.   對於靜態分配的互斥鎖一半用宏賦值的方式初始化

eg:static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;


b.   對於動態分配的互斥鎖(如調用malloc)或分配在共享內存中,則必須調用

pthread_mutex_init(pthread_mutex*mutex, pthread_mutexattr_t *mutexattr)函數來進行初始化。

動態分配釋放例子:
pthread_mutex_init(*mutex ,*attr);
pthread_mutex_destory(*mutex)
pthread_mutexattr_init(*attr)
pthread_mutexattr_destory(*attr)動態分配的代碼:
pthread_mutex_t 
*lock;
lock=(pthread_mutex_t *)malloc(sizeof(szieof(pthread_mutex_t)));
pthread_mutex_init(lock ,NULL);

. 互斥鎖摧毀
pthread_mutex_destory(lock);
free(lock);

銷燬一個互斥鎖即意味着釋放它所佔用的資源,且要求鎖當前處於開放狀態。由於在Linux中,互斥鎖並不佔用任何資源,因此LinuxThreads中的 pthread_mutex_destroy()除了檢查鎖狀態以外(鎖定狀態則返回EBUSY)沒有其他動作。

2.  互斥鎖上鎖、嘗試上鎖和解鎖

        要對公共變量進行上鎖來進行保護,必須最少要上兩個鎖以上,換句話說就得調用pthread_mutex_lock()函數兩次以上,否則,只是上一個鎖會達不到相應的效果。嘗試上鎖pthread_mutex_trylock()和pthread_mutex_lock()的區別是:後者會阻塞等待另外一個鎖被解鎖;前者嘗試去加鎖,如果不成功就返回非0,如果成功返回0,不會產生阻塞。

       每次上鎖之後,一定要解鎖,否則會造成程序一直在阻塞狀態。

四、互斥鎖函數詳解

互斥鎖,是一種信號量,常用來防止兩個進程或線程在同一時刻訪問相同的共享資源。可以保證以下三點:

原子性:把一個互斥量鎖定爲一個原子操作,這意味着操作系統(或pthread函數庫)保證瞭如果一個線程鎖定了一個互斥量,沒有其他線程在同一時間可以成功鎖定這個互斥量。

唯一性:如果一個線程鎖定了一個互斥量,在它解除鎖定之前,沒有其他線程可以鎖定這個互斥量。

非繁忙等待:如果一個線程已經鎖定了一個互斥量,第二個線程又試圖去鎖定這個互斥量,則第二個線程將被掛起(不佔用任何cpu資源),直到第一個線程解除對這個互斥量的鎖定爲止,第二個線程則被喚醒並繼續執行,同時鎖定這個互斥量。

從以上三點,我們看出可以用互斥量來保證對變量(關鍵的代碼段)的排他性訪問。

 

2.函數說明:

需要的頭文件:pthread.h
1
)初始化互斥鎖

函數原型:int  pthread_mutex_init(pthread_mutex_t *mp, const pthread_mutexattr_t *mattr)

參數說明:mp 互斥鎖地址    mattr  屬性 通常默認 null

初始化互斥鎖之前,必須將其所在的內存清零。

如果互斥鎖已初始化,則它會處於未鎖定狀態。互斥鎖可以位於進程之間共享的內存中或者某個進程的專用內存中。

2)鎖定互斥鎖

函數原型:

int pthread_mutex_lock(pthread_mutex_t *mutex); #include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_ mutex_lock(&mp); /* acquire the mutex */

函數說明:

 pthread_mutex_lock() 返回時,該互斥鎖已被鎖定。調用線程是該互斥鎖的屬主。如果該互斥鎖已被另一個線程鎖定和擁有,則調用線程將阻塞,直到該互斥鎖變爲可用爲止。

如果互斥鎖類型爲 PTHREAD_MUTEX_NORMAL則不提供死鎖檢測。嘗試重新鎖定互斥鎖會導致死鎖。如果某個線程嘗試解除鎖定的互斥鎖不是由該線程鎖定或未鎖定,則將產生不確定的行爲。

如果互斥鎖類型爲 PTHREAD_MUTEX_ERRORCHECK則會提供錯誤檢查。如果某個線程嘗試重新鎖定的互斥鎖已經由該線程鎖定,則將返回錯誤。如果某個線程嘗試解除鎖定的互斥鎖不是由該線程鎖定或者未鎖定,則將返回錯誤。

如果互斥鎖類型爲 PTHREAD_MUTEX_RECURSIVE則該互斥鎖會保留鎖定計數這一概念。線程首次成功獲取互斥鎖時,鎖定計數會設置爲 1。線程每重新鎖定該互斥鎖一次,鎖定計數就增加 1。線程每解除鎖定該互斥鎖一次,鎖定計數就減小 1 鎖定計數達到 0 時,該互斥鎖即可供其他線程獲取。如果某個線程嘗試解除鎖定的互斥鎖不是由該線程鎖定或者未鎖定,則將返回錯誤。

如果互斥鎖類型是 PTHREAD_MUTEX_DEFAULT則嘗試以遞歸方式鎖定該互斥鎖將產生不確定的行爲。對於不是由調用線程鎖定的互斥鎖,如果嘗試解除對它的鎖定,則會產生不確定的行爲。如果嘗試解除鎖定尚未鎖定的互斥鎖,則會產生不確定的行爲。

返回值:

pthread_mutex_lock() 在成功完成之後會返回零。其他任何返回值都表示出現了錯誤。如果出現以下任一情況,該函數將失敗並返回對應的值。

EAGAIN:由於已超出了互斥鎖遞歸鎖定的最大次數,因此無法獲取該互斥鎖。

EDEADLK:當前線程已經擁有互斥鎖。

3)解除鎖定互斥鎖

函數原型:

int pthread_mutex_unlock(pthread_mutex_t *mutex); #include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_mutex_unlock(&mutex); /* release the mutex */

函數說明:pthread_mutex_unlock() 可釋放 mutex 引用的互斥鎖對象。互斥鎖的釋放方式取決於互斥鎖的類型屬性。如果調用 pthread_mutex_unlock() 時有多個線程被 mutex 對象阻塞,則互斥鎖變爲可用時調度策略可確定獲取該互斥鎖的線程。對於 PTHREAD_MUTEX_RECURSIVE 類型的互斥鎖,當計數達到零並且調用線程不再對該互斥鎖進行任何鎖定時,該互斥鎖將變爲可用。

返回值:pthread_mutex_unlock() 在成功完成之後會返回零。

其他任何返回值都表示出現了錯誤。如果出現以下情況,該函數將失敗並返回對應的值。

EPERM :當前線程不擁有互斥鎖。

   

4)使用非阻塞互斥鎖鎖定

函數原型:

int pthread_mutex_trylock(pthread_mutex_t *mutex); #include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_mutex_trylock(&mutex); /* try to lock the mutex */

函數說明:pthread_mutex_trylock()  pthread_mutex_lock() 的非阻塞版本。如果 mutex 所引用的互斥對象當前被任何線程(包括當前線程)鎖定,則將立即返回該調用。否則,該互斥鎖將處於鎖定狀態,調用線程是其屬主。

返回值:pthread_mutex_trylock() 在成功完成之後會返回零。其他任何返回值都表示出現了錯誤。如果出現以下任一情況,該函數將失敗並返回對應的值。

EBUSY :

由於 mutex 所指向的互斥鎖已鎖定,因此無法獲取該互斥鎖。

EAGAIN:描述:

由於已超出了 mutex 的遞歸鎖定最大次數,因此無法獲取該互斥鎖。

5)銷燬互斥鎖

函數原型:

int pthread_mutex_destroy(pthread_mutex_t *mp); #include <pthread.h> pthread_mutex_t mp; int ret; ret = pthread_mutex_destroy(&mp); /* mutex is destroyed */請注意,沒有釋放用來存儲互斥鎖的空間。

返回值:

pthread_mutex_destroy() 在成功完成之後會返回零。其他任何返回值都表示出現了錯誤。如果出現以下任一情況,該函數將失敗並返回對應的值。

EINVAL: mp 指定的值不會引用已初始化的互斥鎖對象。

3.例子:

互斥鎖用來保證一段時間內只有一個線程在執行一段代碼。必要性顯而易見:假設各個線程向同一個文件順序寫入數據,最後得到的結果一定是災難性的。

我們先看下面一段代碼。這是一個讀/寫程序,它們公用一個緩衝區,並且我們假定一個緩衝區只能保存一條信息。即緩衝區只有兩個狀態:有信息或沒有信息。

void reader_function ( void );

void writer_function ( void );

char buffer;

int buffer_has_item=0;

pthread_mutex_t mutex;

struct timespec delay;

void main ( void ){

 pthread_t reader;

 /* 定義延遲時間*/

 delay.tv_sec = 2;

 delay.tv_nec = 0;

 /* 用默認屬性初始化一個互斥鎖對象*/

 pthread_mutex_init (&mutex,NULL);

pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);

 writer_function( );

}

void writer_function (void){

 while(1){

  /* 鎖定互斥鎖*/

  pthread_mutex_lock (&mutex);

  if (buffer_has_item==0){

   buffer=make_new_item( );

   buffer_has_item=1;

  }

  /* 打開互斥鎖*/

  pthread_mutex_unlock(&mutex);

  pthread_delay_np(&delay);

 }

}

void reader_function(void){

 while(1){

  pthread_mutex_lock(&mutex);

  if(buffer_has_item==1){

   consume_item(buffer);

   buffer_has_item=0;

  }

  pthread_mutex_unlock(&mutex);

  pthread_delay_np(&delay);

 }

}

程序說明:

這裏聲明瞭互斥鎖變量mutex,結構pthread_mutex_t爲不公開的數據類型,其中包含一個系統分配的屬性對象。函數pthread_mutex_init用來生成一個互斥鎖。NULL參數表明使用默認屬性。如果需要聲明特定屬性的互斥鎖,須調用函數pthread_mutexattr_init。函數pthread_mutexattr_setpshared和函數pthread_mutexattr_settype用來設置互斥鎖屬性。前一個函數設置屬性pshared,它有兩個取值,PTHREAD_PROCESS_PRIVATEPTHREAD_PROCESS_SHARED。前者用來不同進程中的線程同步,後者用於同步本進程的不同線程。

在上面的例子中,我們使用的是默認屬性PTHREAD_PROCESS_ PRIVATE。後者用來設置互斥鎖類型,可選的類型有PTHREAD_MUTEX_NORMALPTHREAD_MUTEX_ERRORCHECKPTHREAD_MUTEX_RECURSIVEPTHREAD _MUTEX_DEFAULT。它們分別定義了不同的上鎖、解鎖機制,一般情況下,選用最後一個默認屬性。

pthread_mutex_lock聲明開始用互斥鎖上鎖,此後的代碼直至調用pthread_mutex_unlock爲止,均被上鎖,即同一時間只能被一個線程調用執行。當一個線程執行到pthread_mutex_lock處時,如果該鎖此時被另一個線程使用,那此線程被阻塞,即程序將等待到另一個線程釋放此互斥鎖。在上面的例子中,我們使用了pthread_delay_np函數,讓線程睡眠一段時間,就是爲了防止一個線程始終佔據此函數。

4.飢餓和死鎖的情形

當一個互斥量已經被別的線程鎖定後,另一個線程調用pthread_mutex_lock()函數去鎖定它時,會掛起自己的線程等待這個互斥量被解鎖。可能出現以下兩種情況:

飢餓狀態”:這個互斥量一直沒有被解鎖,等待鎖定它的線程將一直被掛着,即它請求某個資源,但永遠得不到它。用戶必須在程序中努力避免這種“飢餓”狀態出現。Pthread函數庫不會自動處理這種情況。

死鎖”:一組線程中的所有線程都在等待被同組中另外一些線程佔用的資源,這時,所有線程都因等待互斥量而被掛起,它們中的任何一個都不可能恢復運行,程序無法繼續運行下去。這時就產生了死鎖。Pthread函數庫可以跟蹤這種情形,最後一個線程試圖調用pthread_mutex_lock()時會失敗,並返回類型爲EDEADLK的錯誤。


五、代碼講解:

代碼說明1:互斥鎖基本應用

#include<stdio.h>

#include<pthread.h>

#include"unistd.h"

pthread_mutex_tmutex = PTHREAD_MUTEX_INITIALIZER;

intcount = 0;

void*consume( void *arg)

{

while(1 )

{

pthread_mutex_lock(&mutex );

printf("************************consumebegin lock\n");

printf("************************consumed%d\n",count );

sleep(2);

count++;

printf("************************consumed%d\n",count);

printf("************************consumeover lock\n");

pthread_mutex_unlock(&mutex );

printf("************************I'mout of pthread_mutex\n");

sleep(1);

}

}

void* produce( void * arg )

{

while(1 )

{

pthread_mutex_lock(&mutex );

printf("productbegin lock\n");

printf("Produced %d\n", count );

printf("productover lock\n");

pthread_mutex_unlock(&mutex );

printf("I'mout of pthread_mutex\n");

sleep(1);

}

}

intmain( void )

{

pthread_tthread1,thread2;

pthread_create(&thread1, NULL, &produce, NULL );

pthread_create(&thread2, NULL, &consume, NULL );

pthread_join(thread1,NULL);

pthread_join(thread2,NULL);

return0;

}



結果顯示:

[elbort@elborttest1]$ gcc -Wall -lpthread -o test test.c

[elbort@elborttest1]$ ./test

productbegin lock

Produced0

productover lock

I'mout of pthread_mutex

************************consumebegin lock

************************consumed0

/*中間等待了2秒但是product線程沒有執行|*/

************************consumed1

************************consumeover lock

************************I'mout of pthread_mutex

productbegin lock

Produced1

productover lock

I'mout of pthread_mutex

************************consumebegin lock

************************consumed1

************************consumed2

************************consumeover lock

************************I'mout of pthread_mutex

productbegin lock

Produced2

productover lock

I'mout of pthread_mutex


/************************************************************************/


代碼說明2:單一調用互斥鎖沒效果

void*consume( void *arg)

{

while(1 )

{

pthread_mutex_lock(&mutex );

printf("************************consumebegin lock\n");

printf("************************consumed%d\n",count );

sleep(2);

count++;

printf("************************consumed%d\n",count);

printf("************************consumeover lock\n");

pthread_mutex_unlock(&mutex );

printf("************************I'mout of pthread_mutex\n");

sleep(1);

}

}

void* produce( void * arg )

{

while(1 )

{

count=count+5;

printf("Produced %d\n", count );

sleep(1);

}

}

intmain( void )

{

pthread_tthread1,thread2;

pthread_create(&thread1, NULL, &produce, NULL );

pthread_create(&thread2, NULL, &consume, NULL );

pthread_join(thread1,NULL);

pthread_join(thread2,NULL);

return0;

}

~

~

結果說明:

[elbort@elborttest1]$ ./test

Produced5

************************consumebegin lock

************************consumed5

Produced10 //線程comsume被中斷,conut的值被線程product修改

************************consumed11

************************consumeover lock

************************I'mout of pthread_mutex




/********************************************************/

代碼說明3pthread_mutex_trylock作用

intcount = 0;

void*consume( void *arg)

{

while(1 )

{

pthread_mutex_lock(&mutex );

printf("************************consumebegin lock\n");

printf("************************consumed%d\n",count );

sleep(2);

count++;

printf("************************consumed%d\n",count);

printf("************************consumeover lock\n");

pthread_mutex_unlock(&mutex );

printf("************************I'mout of pthread_mutex\n");

sleep(1);

}

}

void* produce( void * arg )

{

while(1 )

{

if(pthread_mutex_trylock(&mutex ) == 0)

{

printf("productbegin lock\n");

count++;

printf("Produced %d\n", count );

printf("productover lock\n");

pthread_mutex_unlock(&mutex );

printf("I'mout of pthread_mutex\n");

sleep(1);

}

else

{

printf("Ihave try!But i can lock the mutex!\n");

sleep(1);

}

}

}

intmain( void )

{

pthread_tthread1,thread2;

pthread_create(&thread1, NULL, &produce, NULL );

pthread_create(&thread2, NULL, &consume, NULL );

pthread_join(thread1,NULL);

pthread_join(thread2,NULL);

return0;

}


結果顯示:

[elbort@elborttest1]$ ./test

************************consumebegin lock

************************consumed0

Ihave try!But i can lock the mutex!

Ihave try!But i can lock the mutex!

************************consumed1

************************consumeover lock

************************I'mout of pthread_mutex

productbegin lock

Produced2

productover lock

I'mout of pthread_mutex

************************consumebegin lock

************************consumed2

Ihave try!But i can lock the mutex!

Ihave try!But i can lock the mutex!

************************consumed3

************************consumeover lock

************************I'mout of pthread_mutex

productbegin lock

Produced4

productover lock

I'mout of pthread_mutex

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章