linux c編程:互斥鎖


們常說互斥鎖保護臨界區,實際上是說保護臨界區中被多個線程或進程共享的數據。互斥鎖保證任何時刻只有一個線程在執行其中的代碼。

互斥鎖具有以下特點:


·原子性:把一個互斥鎖定義為一個原子操作,這意味着操作系統保證了如果一個線程鎖定了互斥鎖,則沒有其他線程可以在同一時間成功鎖定這個互斥量。


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


·非繁忙等待:如果一個線程已經鎖定了一個互斥鎖,第二個線程又試圖去鎖定這個互斥鎖,則第二個線程將被掛起(不占用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參數:

程序運行結果如下

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM