Linux同步機制(一) - 線程鎖

1 互斥鎖

在線程實際運行過程中,我們經常需要多個線程保持同步。

這時可以用互斥鎖來完成任務。互斥鎖的使用過程中,主要有

pthread_mutex_init

pthread_mutex_destory

pthread_mutex_lock

pthread_mutex_unlock

這幾個函數以完成鎖的初始化,鎖的銷燬,上鎖和釋放鎖操作。

1.1 鎖的創建

鎖可以被動態或靜態創建,可以用宏PTHREAD_MUTEX_INITIALIZER來靜態的初始化鎖,採用這種方式比較容易理解,互斥鎖是pthread_mutex_t的結構體,而這個宏是一個結構常量,如下可以完成靜態的初始化鎖:

pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;

另外鎖可以用pthread_mutex_init函數動態的創建,函數原型如下:

int pthread_mutex_init(pthread_mutex_t*mutex, const pthread_mutexattr_t * attr)

1.2 鎖的屬性

互斥鎖屬性可以由pthread_mutexattr_init(pthread_mutexattr_t *mattr)來初始化,然後可以調用其他的屬性設置方法來設置其屬性。

互斥鎖的範圍:可以指定是該進程與其他進程的同步還是同一進程內不同的線程之間的同步。可以設置爲PTHREAD_PROCESS_SHARE和PTHREAD_PROCESS_PRIVATE。默認是後者,表示進程內使用鎖。可以使用

int pthread_mutexattr_setpshared(pthread_mutexattr_t*mattr, int pshared)

pthread_mutexattr_getpshared(pthread_mutexattr_t*mattr,int *pshared)

用來設置與獲取鎖的範圍;

互斥鎖的類型:有以下幾個取值空間:

PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當一個線程加鎖以後,其餘請求鎖的線程將形成一個等待隊列,並在解鎖後按優先級獲得鎖。這種鎖策略保證了資源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個線程對同一個鎖成功獲得多次,並通過多次unlock解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新競爭。
PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個線程請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同。這樣就保證當不允許多次加鎖時不會出現最簡單情況下的死鎖。
PTHREAD_MUTEX_ADAPTIVE_NP,適應鎖,動作最簡單的鎖類型,僅等待解鎖後重新競爭。

可以用
pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type)
pthread_mutexattr_gettype(pthread_mutexattr_t *attr , int *type)

獲取或設置鎖的類型。

1.3 鎖的釋放

調用pthread_mutex_destory之後,可以釋放鎖佔用的資源,但這有一個前提上鎖當前是沒有被鎖的狀態。

1.4 鎖操作

對鎖的操作主要包括加鎖 pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖pthread_mutex_trylock()三個。

int pthread_mutex_lock(pthread_mutex_t*mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)

pthread_mutex_trylock()語義與pthread_mutex_lock()類似,不同的是在鎖已經被佔據時返回EBUSY而不是掛起等待。

1.5 代碼講解:

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

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int count = 0;

void* consume(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        printf("************************consume begin lock\n");  
        printf("************************consumed %d\n",count);  
        count++;
        sleep(2);
        printf("************************consume over lock\n"); 
        pthread_mutex_unlock(&mutex); 
        printf("************************I'm out of pthread_mutex\n"); 
        sleep(1);
    }
    
    return NULL;
}

void* produce( void * arg )
{
    while(1)
    {
        pthread_mutex_lock(&mutex );
        printf("product begin lock\n");
        printf("produced %d\n", count);
        printf("product over lock\n");
        pthread_mutex_unlock(&mutex );
        printf("I'm out of pthread_mutex\n");
        sleep(1);
    }
    
    return NULL;
}

int main( void )
{
    pthread_t thread1,thread2;
    pthread_create(&thread1, NULL, &produce, NULL );
    pthread_create(&thread2, NULL, &consume, NULL );
    pthread_join(thread1,NULL);
    pthread_join(thread2,NULL);
    return 0;
}

結果說明:

[root@rocket lock-free]# g++ -g -o pthread_mutex_lockpthread_mutex_lock.cpp -lpthread

[root@rocket lock-free]#./pthread_mutex_lock

product begin lock

produced 0

product over lock

I'm out of pthread_mutex

************************consume beginlock

************************consumed 0

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

************************consume overlock

************************I'm out ofpthread_mutex

product begin lock

produced 1

product over lock

I'm out of pthread_mutex

product begin lock

produced 1

product over lock

I'm out of pthread_mutex

************************consume beginlock

************************consumed 1

************************consume overlock

************************I'm out ofpthread_mutex

product begin lock

produced 2

product over lock

I'm out of pthread_mutex

************************consume beginlock

************************consumed 2

************************consume overlock

************************I'm out ofpthread_mutex

 

代碼說明2:pthread_mutext_trylock使用

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int count = 0;

void* consume(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        printf("************************consume begin lock\n");  
        printf("************************consumed %d\n",count);  
        count++;
        sleep(2);
        printf("************************consume over lock\n"); 
        pthread_mutex_unlock(&mutex); 
        printf("************************I'm out of pthread_mutex\n"); 
        sleep(1);
    }
    
    return NULL;
}

void* produce( void * arg )
{
    while(1)
    {
        if(pthread_mutex_trylock(&mutex ) == 0)
        {
            printf("product begin lock\n");
            printf("produced %d\n", count );
            printf("product over lock\n");
            pthread_mutex_unlock(&mutex);
            printf("I'm out of pthread_mutex\n");
            sleep(1);
        }
        else
        {
            printf("I have try!But i can`t lock the mutex!\n");
            sleep(1);
        }
    }
    
    return NULL;
}

int main( void )
{
    pthread_t thread1,thread2;
    pthread_create(&thread1, NULL, &produce, NULL );
    pthread_create(&thread2, NULL, &consume, NULL );
    pthread_join(thread1,NULL);
    pthread_join(thread2,NULL);
    return 0;
}


結果說明:

[root@rocket lock-free]# g++ -g -o pthread_mutex_trylock pthread_mutex_trylock.cpp -lpthread

[root@rocket lock-free]#./pthread_mutex_trylock

************************consume beginlock

************************consumed 0

/* trylock沒有成功馬上返回! */

I have try!But i can`t lock the mutex!

I have try!But i can`t lock the mutex!

************************consume overlock

************************I'm out ofpthread_mutex

product begin lock

produced 1

product over lock

I'm out of pthread_mutex

************************consume beginlock

************************consumed 1

I have try!But i can`t lock the mutex!

I have try!But i can`t lock the mutex!

************************consume overlock

************************I'm out ofpthread_mutex

product begin lock

produced 2

product over lock

I'm out of pthread_mutex

************************consume beginlock

************************consumed 2

I have try!But i can`t lock the mutex!

I have try!But i can`t lock the mutex!

************************consume overlock

************************I'm out ofpthread_mutex

 

 

2 讀寫鎖

讀寫鎖是因爲有3種狀態,所以可以有更高的並行性。

2.1 特性

一次只有一個線程可以佔有寫模式的讀寫鎖, 但是可以有多個線程同時佔有讀模式的讀寫鎖,正是因爲這個特性,當讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖之前, 所有試圖對這個鎖加鎖的線程都會被阻塞。

當讀寫鎖在讀加鎖狀態時, 所有試圖以讀模式對它進行加鎖的線程都可以得到訪問權, 但是如果線程希望以寫模式對此鎖進行加鎖, 它必須阻塞直到所有的線程釋放鎖。

通常,當讀寫鎖處於讀模式鎖住狀態時,如果有另外線程試圖以寫模式加鎖,讀寫鎖通常會阻塞隨後的讀模式鎖請求, 這樣可以避免讀模式鎖長期佔用, 而等待的寫模式鎖請求長期阻塞。

2.2 適用性

讀寫鎖適合於對數據結構的讀次數比寫次數多得多的情況。因爲,讀模式鎖定時可以共享, 以寫模式鎖住時意味着獨佔, 所以讀寫鎖又叫共享-獨佔鎖。

2.3 API初始化和銷燬

#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
成功則返回0,出錯則返回錯誤編號

同互斥鎖一樣,在釋放讀寫鎖佔用的內存之前,需要先通過pthread_rwlock_destroy對讀寫鎖進行清理工作, 釋放由init分配的資源。

2.4 讀和寫

#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

這3個函數分別實現獲取讀鎖, 獲取寫鎖和釋放鎖的操作. 獲取鎖的兩個函數是阻塞操作

 

同樣,非阻塞的函數爲:

#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

非阻塞的獲取鎖操作, 如果可以獲取則返回0,否則返回錯誤的EBUSY

 

2.5 代碼講解

代碼說明1:讀寫鎖基本應用

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <bits/pthreadtypes.h>
 
static pthread_rwlock_t rwlock; //讀寫鎖對象

int count = 0;

void *thread_function_read(void *arg)
{
    while(1)
    {
        pthread_rwlock_rdlock(&rwlock);
        printf("************************%d, read count %d\n", pthread_self(), count);
        sleep(1);
        pthread_rwlock_unlock(&rwlock);
	usleep(100);
    }
    
    return NULL;
}

void *thread_function_write(void *arg)
{
    while(1)
    {
        pthread_rwlock_wrlock(&rwlock);
        count++;
        printf("************************%d, write count %d\n", pthread_self(), count);
        sleep(5);
        pthread_rwlock_unlock(&rwlock);
	usleep(100);
    }
    return NULL;
}
   
int main(int argc, char *argv[])
{
    pthread_t rpthread1, rpthread2, wpthread;

    pthread_rwlock_init(&rwlock,NULL);

    pthread_create(&rpthread1, NULL, thread_function_read, NULL);
    pthread_create(&rpthread2, NULL, thread_function_read, NULL);
    pthread_create(&wpthread, NULL, thread_function_write, NULL);

    pthread_join(rpthread1, NULL);           
    pthread_join(rpthread2, NULL);           
    pthread_join(wpthread, NULL);           
               
    pthread_rwlock_destroy(&rwlock);          
    exit(EXIT_SUCCESS);
}

結果說明:

[root@rocket lock-free]#./pthread_rwlock

/* 2個讀線程互相不阻塞 */

************************1442944768,read count 0 

************************1432454912,read count 0

/* 寫線程阻塞所有其它線程 */

************************1421965056,write count 1

************************1442944768,read count 1

************************1432454912,read count 1

************************1421965056,write count 2

************************1442944768,read count 2

************************1432454912,read count 2

************************1421965056,write count 3

************************1442944768,read count 3

************************1432454912,read count 3

************************1421965056,write count 4

 

有意思的是,加入去掉上面代碼中thread_function_read和thread_function_write中的usleep(100),則會出現以下結果

[root@rocket lock-free]#./pthread_rwlock

************************-1896831232,read count 0

************************-1907321088,read count 0

************************-1907321088,read count 0

************************-1896831232,read count 0

************************-1907321088,read count 0

************************-1896831232,read count 0

************************-1907321088,read count 0

發現搶不到寫鎖,按我原先的理解,因爲reader線程先啓動,所以首先是reader搶到鎖,reader搶到鎖以後,writer阻塞在鎖請求上,當reader釋放以後,應該輪到writer纔對啊,可是不是這樣的!當reader釋放後再次請求鎖時,還是能拿到!writer基本搶不到鎖!

查手冊寫到,"The pthread_rwlock_rdlock() function applies a read lock tothe read-write lock referenced by rwlock. The calling thread acquires the readlock if a writer does not hold the lock and there are no writers blocked on thelock. It is unspecified whether the calling thread acquires the lock when awriter does not hold the lock and there are writers waiting for the lock" 意思就是說,沒有writer在等寫鎖的時辰,reader是可以拿到讀鎖的。然則沒有劃定,若是有writer在期待寫鎖,該若何?

還好,Linux有pthread_rwlockattr_setkind_np這個函數。

enum

{

 PTHREAD_RWLOCK_PREFER_READER_NP,

 PTHREAD_RWLOCK_PREFER_WRITER_NP,

 PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP,

 PTHREAD_RWLOCK_DEFAULT_NP =PTHREAD_RWLOCK_PREFER_READER_NP

};

可是直接pthread_rwlockattr_setkind_np(&attr,PTHREAD_RWLOCK_PREFER_WRITER_NP);

沒用!爲啥呢?連man頁都沒有,所以我思疑這個函數沒實現,因而就用debuginfo-install glibc? 裝glibc的調試符號,然後用gdb跟進去,發現pthread_rwlockattr_setkind_np確切是有實現的,代碼很簡單,更改了attr的一個成員變量。那是爲啥呢?

再谷歌,終究找到了pthread_rwlockattr_setkind_np的man page,末尾有一段notes,讓我年夜汗:

“Setting the value read-write lockkind to PTHREAD_RWLOCK_PREFER_WRITER_NP, results in the same behavior assetting the value to PTHREAD_RWLOCK_PREFER_READER_NP. As long as a readerthread holds the lock the thread holding a write lock will be starved. Settingthe kind value to PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP, allows thewriter to run. However, the writer may not be recursive as is implied by thename. “

意思就是說,

PTHREAD_RWLOCK_PREFER_WRITER_NP和PTHREAD_RWLOCK_PREFER_READER_NP是一樣滴!應當設置成PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP纔對!可是PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP也是名存實亡滴,它纔不會recursive 呢。

 

這樣就有了代碼說明2:讀寫鎖優先級的使用

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <bits/pthreadtypes.h>
 
static pthread_rwlock_t rwlock; //讀寫鎖對象

int count = 0;

void *thread_function_read(void *arg)
{
    while(1)
    {
        pthread_rwlock_rdlock(&rwlock);
        printf("************************%d, read count %d\n", pthread_self(), count);
        sleep(1);
        pthread_rwlock_unlock(&rwlock);
	//usleep(100);
    }
    
    return NULL;
}

void *thread_function_write(void *arg)
{
    while(1)
    {
	pthread_rwlock_wrlock(&rwlock);
        count++;
        printf("************************%d, write count %d\n", pthread_self(), count);
        sleep(1);
        pthread_rwlock_unlock(&rwlock);
	usleep(100);
    }
    return NULL;
}
   
int main(int argc, char *argv[])
{
    pthread_t rpthread1, rpthread2, wpthread;
    
    pthread_rwlockattr_t attr;    
    pthread_rwlockattr_setkind_np(&attr,PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
    pthread_rwlock_init(&rwlock, &attr);

    pthread_create(&rpthread1, NULL, thread_function_read, NULL);
    pthread_create(&rpthread2, NULL, thread_function_read, NULL);
    pthread_create(&wpthread, NULL, thread_function_write, NULL);

    pthread_join(rpthread1, NULL);           
    pthread_join(rpthread2, NULL);           
    pthread_join(wpthread, NULL);           
               
    pthread_rwlock_destroy(&rwlock);          
    exit(EXIT_SUCCESS);
}


運行結果:

[root@rocket lock-free]#./pthread_rwlock_withpriority

************************1529054976,read count 0

************************1518565120,read count 0

************************1508075264,write count 1

************************1529054976,read count 1

************************1518565120,read count 1

************************1508075264,write count 2

************************1529054976,read count 2

************************1518565120,read count 2

************************1508075264,write count 3

這樣就不會導致writer餓死。

 

 

代碼說明3:pthread_rwlock_tryrdlock使用

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <bits/pthreadtypes.h>
 
static pthread_rwlock_t rwlock; //讀寫鎖對象

int count = 0;

void *thread_function_read(void *arg)
{
    int print_count = 0;
    while(1)
    {
        if (pthread_rwlock_tryrdlock(&rwlock) == 0)
	{
		printf("************************%d, read count %d\n", pthread_self(), count);
		sleep(1);
		pthread_rwlock_unlock(&rwlock);
		usleep(100);
	}
        else
	{
		print_count++;
		if (print_count % 10 == 0)
		{
			printf("I have try!But i can`t lock the rdlock!\n");
			print_count = 0;
		}
			
		usleep(100);
	}
    }
    
    return NULL;
}

void *thread_function_write(void *arg)
{
    while(1)
    {
        pthread_rwlock_wrlock(&rwlock);
        count++;
        printf("************************%d, write count %d\n", pthread_self(), count);
        sleep(5);
        pthread_rwlock_unlock(&rwlock);
	usleep(100);
    }
    return NULL;
}
   
int main(int argc, char *argv[])
{
    pthread_t rpthread1, rpthread2, wpthread;

    pthread_rwlock_init(&rwlock,NULL);

    pthread_create(&rpthread1, NULL, thread_function_read, NULL);
    pthread_create(&rpthread2, NULL, thread_function_read, NULL);
    pthread_create(&wpthread, NULL, thread_function_write, NULL);

    pthread_join(rpthread1, NULL);           
    pthread_join(rpthread2, NULL);           
    pthread_join(wpthread, NULL);           
               
    pthread_rwlock_destroy(&rwlock);          
    exit(EXIT_SUCCESS);
}


結果說明:

************************1819674368,read count 0

************************1809184512,read count 0

************************1798694656,write count 1

/* trylock沒有成功馬上返回! */

I have try!But i can`t lock therdlock!

I have try!But i can`t lock therdlock!

I have try!But i can`t lock therdlock!

I have try!But i can`t lock therdlock!

************************1819674368,read count 1

************************1809184512,read count 1

************************1798694656, writecount 2

I have try!But i can`t lock therdlock!

I have try!But i can`t lock therdlock!

I have try!But i can`t lock therdlock!

I have try!But i can`t lock therdlock!

 

3 自旋鎖

自旋鎖是SMP架構中的一種low-level的同步機制。
當線程A想要獲取一把自旋鎖而該鎖又被其它線程鎖持有時,線程A會在一個循環中自旋以檢測鎖是不是已經可用了。對於自旋鎖需要注意:

由於自旋時不釋放CPU,因而持有自旋鎖的線程應該儘快釋放自旋鎖,否則等待該自旋鎖的線程會一直在那裏自旋,這就會浪費CPU時間。

持有自旋鎖的線程在sleep之前應該釋放自旋鎖以便其它線程可以獲得自旋鎖。(在內核編程中,如果持有自旋鎖的代碼sleep了就可能導致整個系統掛起)

Pthreads提供的與Spin Lock鎖操作相關的API主要有:

intpthread_spin_destroy(pthread_spinlock_t *);

int pthread_spin_init(pthread_spinlock_t*, int);

intpthread_spin_lock(pthread_spinlock_t *);

intpthread_spin_trylock(pthread_spinlock_t *);

intpthread_spin_unlock(pthread_spinlock_t *);

3.1 初始化自旋鎖

pthread_spin_init用來申請使用自旋鎖所需要的資源並且將它初始化爲非鎖定狀態。pshared的取值及其含義:

PTHREAD_PROCESS_SHARED:該自旋鎖可以在多個進程中的線程之間共享。

PTHREAD_PROCESS_PRIVATE:僅初始化本自旋鎖的線程所在的進程內的線程才能夠使用該自旋鎖。

3.2 獲得一個自旋鎖

pthread_spin_lock用來獲取(鎖定)指定的自旋鎖. 如果該自旋鎖當前沒有被其它線程所持有,則調用該函數的線程獲得該自旋鎖.否則該函數在獲得自旋鎖之前不會返回。如果調用該函數的線程在調用該函數時已經持有了該自旋鎖,則結果是不確定的。

3.3 嘗試獲取一個自旋鎖

pthread_spin_trylock會嘗試獲取指定的自旋鎖,如果無法獲取則理解返回失敗。

3.4 釋放(解鎖)一個自旋鎖

pthread_spin_unlock用於釋放指定的自旋鎖。

3.5 銷燬一個自旋鎖

pthread_spin_destroy用來銷燬指定的自旋鎖並釋放所有相關聯的資源(所謂的所有指的是由pthread_spin_init自動申請的資源)在調用該函數之後如果沒有調用pthread_spin_init重新初始化自旋鎖,則任何嘗試使用該鎖的調用的結果都是未定義的。如果調用該函數時自旋鎖正在被使用或者自旋鎖未被初始化則結果是未定義的。

 

4 特性對比

鎖類型

鎖特性

適用場景

互斥鎖mutex

會導致線程切換

一般情況下的首選

讀寫鎖rwlock

同一時間只能有一個writer

可以同時有多個reader

讀多寫少的場景

自旋鎖spinlock

不會導致線程切換

會導致CPU利用率升高

適合小代碼段

小代碼段,加鎖不是很頻繁的場景



 

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