們常說互斥鎖保護臨界區,實際上是說保護臨界區中被多個線程或進程共享的數據。互斥鎖保證任何時刻只有一個線程
在執行其中的代碼。
互斥鎖具有以下特點:
·
原子性:把一個互斥鎖定義為一個原子操作,這意味着操作系統保證了如果一個線程鎖定了互斥鎖,則沒有其他線程可以在同一時間成功鎖定這個互斥量。
·
唯一性:如果一個線程鎖定一個互斥量,在它接觸鎖定之前,沒有其他線程可以鎖定這個互斥量。
·
非繁忙等待:如果一個線程已經鎖定了一個互斥鎖,第二個線程又試圖去鎖定這個互斥鎖,則第二個線程將被掛起(不占用
CPU
資源),直到第一個線程解鎖,第二個線程則被喚醒並繼續執行,同時鎖定這個互斥量
創建互斥鎖需要用到下面幾個函數和變量:
在使用互斥鎖之前,需要先創建一個互斥鎖的對象。 互斥鎖的類型是
pthread_mutex_t
,所以定義一個變量就是創建了一個互斥鎖。
pthread_mutex_t mtx; 這就定義了一個互斥鎖。但是如果想使用這個互斥鎖還是不行的,我們還需要對這個互斥鎖進行初始化, 使用函數 pthread_mutex_init() 對互斥鎖進行初始化操作。 pthread_mutex_init(&mtx, NULL);
除了使用 pthread_mutex_init() 初始化一個互斥鎖,我們還可以使用下面的方式定義一個互斥鎖:
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
在頭文件 /usr/include/pthread.h 中,對 PTHREAD_MUTEX_INITIALIZER 的聲明如下
# define PTHREAD_MUTEX_INITIALIZER \ { { 0, 0, 0, 0, 0, 0, { 0, 0 } } } 為什么可以這樣初始化呢,因為互斥鎖的類型 pthread_mutex_t 是一個聯合體, 其聲明在文件 /usr/include/bits/pthreadtypes.h 中,代碼如下: /* Data structures for mutex handling. The structure of the attribute type is not exposed on purpose. */ typedef union { struct __pthread_mutex_s { int __lock; unsigned int __count; int __owner; #if __WORDSIZE == 64 unsigned int __nusers; #endif /* KIND must stay at this position in the structure to maintain binary compatibility. */ int __kind; #if __WORDSIZE == 64 int __spins; __pthread_list_t __list; # define __PTHREAD_MUTEX_HAVE_PREV 1 #else unsigned int __nusers; __extension__ union { int __spins; __pthread_slist_t __list; }; #endif } __data; char __size[__SIZEOF_PTHREAD_MUTEX_T]; long int __align; } pthread_mutex_t; 獲取互斥鎖:
接下來是如何使用互斥鎖進行互斥操作。在進行互斥操作的時候, 應該先"拿到鎖"再執行需要互斥的操作,否則可能會導致多個線程都需要訪問的數據結果不一致。 例如在一個線程在試圖修改一個變量的時候,另一個線程也試圖去修改這個變量, 那就很可能讓后修改的這個線程把前面線程所做的修改覆蓋了。
下面是獲取鎖的操作:
阻塞調用
pthread_mutex_lock(&mtx);
這個操作是阻塞調用的,也就是說,如果這個鎖此時正在被其它線程占用, 那么 pthread_mutex_lock() 調用會進入到這個鎖的排隊隊列中,並會進入阻塞狀態, 直到拿到鎖之后才會返回。
非阻塞調用
如果不想阻塞,而是想嘗試獲取一下,如果鎖被占用咱就不用,如果沒被占用那就用, 這該怎么實現呢?可以使用 pthread_mutex_trylock() 函數。 這個函數和 pthread_mutex_lock() 用法一樣,只不過當請求的鎖正在被占用的時候, 不會進入阻塞狀態,而是立刻返回,並返回一個錯誤代碼 EBUSY,意思是說, 有其它線程正在使用這個鎖。
int err = pthread_mutex_trylock(&mtx); if(0 != err) { if(EBUSY == err) { //The mutex could not be acquired because it was already locked. } }
超時調用
如果不想不斷的調用 pthread_mutex_trylock() 來測試互斥鎖是否可用, 而是想阻塞調用,但是增加一個超時時間呢,那么可以使用 pthread_mutex_timedlock() 來解決, 其調用方式如下:
struct timespec abs_timeout; abs_timeout.tv_sec = time(NULL) + 1; abs_timeout.tv_nsec = 0; int err = pthread_mutex_timedlock(&mtx, &abs_timeout); if(0 != err) { if(ETIMEDOUT == err) { //The mutex could not be locked before the specified timeout expired. } }
上面代碼的意思是,阻塞等待線程鎖,但是只等1秒鍾,一秒鍾后如果還沒拿到鎖的話, 那就返回,並返回一個錯誤代碼 ETIMEDOUT,意思是超時了。
其中 timespec 定義在頭文件 time.h 中,其定義如下
struct timespec { __time_t tv_sec; /* Seconds. */ long int tv_nsec; /* Nanoseconds. */ };
釋放互斥鎖
用完了互斥鎖,一定要記得釋放,不然下一個想要獲得這個鎖的線程, 就只能去等着了,如果那個線程很不幸的使用了阻塞等待,那就悲催了。
釋放互斥鎖比較簡單,使用 pthread_mutex_unlock() 即可:
pthread_mutex_unlock(&mtx);
同步中有一個稱為生產者
-
消費者(
producer-consumer
)的經典問題。一個或多個生產者(線程或進程)產生一個個數據條目,這些條目由一個或多個消費者(線程或進程)處理。數據條目在生產者和消費者之間通過某種類型的
IPC
傳遞。
這里以生產者和消費者為例,模型如下:
根據這個模型
可以構造相關的代碼:
#include <pthread.h>
#include <stdio.h>
#define MAXNITEMS 1000000
#define MAXNTHREADS 5
int nitems;
struct
{
pthread_mutex_t mutex;
int buff[MAXNITEMS];
int nput;
int nval;
}shared={PTHREAD_MUTEX_INITIALIZER};
void *produce(void *arg)
{
for(;;)
{
pthread_mutex_lock(&shared.mutex);
if(shared.nput >= nitems)
{
pthread_mutex_unlock(&shared.mutex);
return NULL;
}
shared.buff[shared.nput]=shared.nval;
shared.nput++;
shared.nval++;
pthread_mutex_unlock(&shared.mutex);
*((int *)arg)+=1;
}
}
void *consumer(void *arg)
{
int i;
for(i=0;i<=nitems;i++)
{
if(shared.buff[i] != i)
printf("buff[%d]=%d\n",i,shared.buff[i]);
}
return NULL;
}
int produce_consumer()
{
int i,nthreads,count[MAXNTHREADS];
pthread_t tid_produce[MAXNTHREADS],tid_consume;
nitems=MAXNITEMS;
nthreads=MAXNTHREADS;
pthread_setconcurrency(nthreads);
for(i=0;i<nthreads;i++)
{
count[i]=0;
pthread_create(&tid_produce[i],NULL,produce,&count[i]);
}
for(i=0;i<nthreads;i++)
{
pthread_join(tid_produce[i],NULL);
printf("count[%d]=%d\n",i,count[i]);
}
pthread_create(&tid_consume,NULL,consumer,NULL);
pthread_join(tid_consume,NULL);
return 1;
}
1
首先我們創建生產者線程,每個線程執行
produce
。在
tid_produce
數組中保存每個線程的線程
ID
。傳遞給每個生產者線程的參數是指向
count
數組中某個元素的指針。首先把該計數器初始化為
0.
然后每個線程在每次往緩沖區中存放一個條目時給這個計數器加
1
。當一切生成完畢時,我們輸出這個計數器數組各元素的值,以查看每個生產者線程分別存放了多少條目
2
等待所有生產者線程終止,同時輸出每個線程的計數器值,此后才啟動單個消費者線程
程序編譯報錯:
pthread.c:(.text+0x85)
:對‘
pthread_create’
未定義的引用。
由於
pthread
庫不是
Linux
系統默認的庫,連接時需要使用庫
libpthread.a
、
codeblocks
編譯器中加入
libpthread.so
的共享庫
或者在用GCC編譯程序的時候加上-lpthread
參數
:
程序運行結果如下
: