并發編程–互斥鎖與讀寫鎖
文章目錄
- 并發編程--互斥鎖與讀寫鎖
- 1. 基本概念
- 2. 互斥鎖
- 2.1 基本邏輯
- 2.2 函數接口
- 2.3示例代碼1
- 2.4示例代碼2
- 3. 讀寫鎖
- 3.1 基本邏輯
- 3.2示例代碼
1. 基本概念
互斥與同步是最基本的邏輯概念:
- 互斥指的是控制兩個進度使之互相排斥,不同時運行。
- 同步指的是控制兩個進度使之有先有后,次序可控。
2. 互斥鎖
2.1 基本邏輯
使得多線程間互斥運行的最簡單辦法,就是增加一個互斥鎖。任何一條線成要開始運行互斥區間的代碼,都必須先獲取互斥鎖,而互斥鎖的本質是一個二值信號量,因此當其中一條線程搶先獲取了互斥鎖之后,其余線程就無法再次獲取了,效果相當于給相關的資源加了把鎖,直到使用者主動解鎖,其余線程方可有機會獲取這把鎖。
2.2 函數接口
定義
互斥鎖是一個特殊的變量,定義如下:
#include <pthread>
pthread_mutex_t m;
一般而言,由于互斥鎖需要被多條線程使用,因此一般會將互斥鎖定義為全局變量。
初始化與銷毀
未經初始化的互斥鎖是無法使用的,初始化互斥鎖有兩種辦法:
- 靜態初始化
- 動態初始化
靜態初始化很簡單,就是在定義同時賦予其初值:
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
由于靜態初始化互斥鎖不涉及動態內存,因此無需顯式釋放互斥鎖資源,互斥鎖將會伴隨程序一直存在,直到程序退出為止。而所謂動態初始化指的是使用 pthread_mutex_init()
給互斥鎖分配動態內存并賦予初始值,因此這種情形下的互斥鎖需要在用完之后顯式地進行釋放資源,接口如下:
#include <pthread.h>// 初始化互斥鎖
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);// 銷毀互斥鎖
int pthread_mutex_destroy(pthread_mutex_t *mutex);
接口說明:
- mutex:互斥鎖
- attr:互斥鎖屬性(一般設置為NULL)
加鎖與解鎖
互斥鎖的基本操作就是加鎖與解鎖,接口如下:
#include <pthread.h>
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;// 加鎖
pthread_mutex_lock( &m );// 解鎖
pthread_mutex_unlock( &m );
2.3示例代碼1
將此前判斷偶數的代碼用互斥鎖加以改進如下:
// concurrency.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <pthread.h>pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;int global = 100;void *isPrime(void *arg)
{while(1){pthread_mutex_lock(&m);// 一段樸素的代碼if(global%2 == 0)printf("%d是偶數\n", global);pthread_mutex_unlock(&m);}
}int main()
{pthread_t tid;pthread_create(&tid, NULL, isPrime, NULL);// 一條人畜無害的賦值語句while(1){pthread_mutex_lock(&m);global = rand() % 5000;pthread_mutex_unlock(&m);}
}
運行結果如下:
gec@ubuntu:~$ ./concurrency
492是偶數
2362是偶數
2778是偶數
3926是偶數
540是偶數
3426是偶數
4172是偶數
112是偶數
368是偶數
2576是偶數
1530是偶數
1530是偶數
2862是偶數
4706是偶數
...
gec@ubuntu:~$
可見,有了互斥鎖之后,輸出的結果正確了。
2.4示例代碼2
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>pthread_mutex_t m;void *routine(void *arg)
{char *msg = (char *)arg;#ifdef MUTEXpthread_mutex_lock(&m);#endifwhile(*msg != '\0'){fprintf(stderr,"%c",*msg);usleep(100);msg++;}#ifdef MUTEXpthread_mutex_unlock(&m);#endifpthread_exit(NULL);
}int main(void)
{pthread_mutex_init(&m,NULL);pthread_t t1,t2;pthread_create(&t1,NULL,routine,"AAAAAAAAAAA");pthread_create(&t2,NULL,routine,"BBBBBBBBBBB");pthread_exit(NULL);
}
通過宏定義實現代碼的不同運行,輸出不同的結果。若不使用互斥鎖的話,則直接運行,結果將會是AB交互是輸出,兩個線程t1,t2會同時運行,交互式輸出;若使用互斥鎖的話,會輸出單獨輸出一個線程的結果,然后再輸出另一個線程的結果。
若要使用互斥鎖則如下:
gcc pthread_mutex.c -o pthread_mutex -lpthread -DMUTEX
3. 讀寫鎖
3.1 基本邏輯
對于互斥鎖而言,凡是涉及臨界資源的訪問一律加鎖,這在并發讀操作的場景下會大量浪費時間。要想提高訪問效率,就必須要將對資源的讀寫操作加以區分:讀操作可以多任務并發執行,只有寫操作才進行恰當的互斥。這就是讀寫鎖的設計來源。
讀寫鎖提高了資源訪問的效率
定義
與互斥鎖類似,讀寫鎖也是一種特殊的變量:
pthread_rwlock_t rw;
初始化
與互斥鎖類似,讀寫鎖也分成靜態初始化和動態初始化:
#include <pthread.h>// 靜態初始化:
pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER;// 動態初始化與銷毀:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
加鎖
讀寫鎖最大的特點是對即將要做的讀寫操作做了區分:
- 讀操作可以共享,因此多條線程可以對同一個讀寫鎖加多重讀鎖
- 寫操作天然互斥,因此多條線程只能有一個擁有寫鎖。(注意寫鎖與讀鎖也是互斥的)
#include <pthread.h>// 讀鎖
// 1,阻塞版本
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
// 2,非阻塞版本
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);// 寫鎖
// 1,阻塞版本
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
// 2,非阻塞版本
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
操作原則:
- 如果只對數據進行讀操作,那么就加 → 讀鎖。
- 如果要對數據進行寫操作,那么就加 → 寫鎖。
解鎖
#include <pthread.h>int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
3.2示例代碼
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>char global = 'X';pthread_rwlock_t rwlock;void *routine(void *arg)
{#ifdef RDLOCKpthread_rwlock_rdlock(&rwlock);#elif WRLOCKpthread_rwlock_wrlock(&rwlock);#endifint i = 1000;while(i > 0){fprintf(stderr,"[%c:%c]",*(char*)arg,global);i--;}pthread_rwlock_unlock(&rwlock);pthread_exit(NULL);
}int main(void)
{pthread_rwlock_init(&rwlock,NULL);pthread_t t1,t2,t3;pthread_create(&t1,NULL,routine,"1");pthread_create(&t2,NULL,routine,"2");pthread_create(&t3,NULL,routine,"3");pthread_exit(NULL);
}