(轉自:https://blog.csdn.net/u010304442/article/details/90449716)
1.互斥鎖
在線程實際運行過程中,我們經常需要多個線程保持同步。這時可以用互斥鎖來完成任務。
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);
其中:
mutex:所需創建的鎖;
attr:創建鎖的屬性。一般默認爲NULL,分爲以下幾個屬性:
* PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當一個線程加鎖以後,其餘請求鎖的線程將形成一個等待隊列,並在解鎖後
按優先級獲得鎖。這種鎖策略保證了資源分配的公平性;
* PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個線程對同一個鎖成功獲得多次,並通過多次unlock解鎖。如果是不同線程請求
,則在加鎖線程解鎖時重新競爭;
* PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個線程請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP
類型動作相同。這樣就保證當不允許多次加鎖時不會出現最簡單情況下的死鎖;
* PTHREAD_MUTEX_ADAPTIVE_NP,適應鎖,動作最簡單的鎖類型,僅等待解鎖後重新競爭;
函數成功執行後,互斥鎖被初始化爲鎖住態。
1.2鎖操作
對鎖的操作主要包括加鎖 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.3鎖銷燬
創建的互斥鎖在不使用的時候需要消耗,不然會造成系統資源的流失,其函數原型如下:
int pthread_mutexattr_destroy (pthread_mutex_t *mutex);
1.4示例代碼
#include <stdio.h>
#include <unistd.h>
#include <pthread.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;
}
2.讀寫鎖
讀寫鎖: 我們在編寫多線程的時候,我們可能需要經常去讀取某個共享數據變量,但是相對要改寫這個變量的機會相對較少。在讀的過程中,往往伴隨着查找的操作,中間耗時很長,給這種代碼加鎖,會極大的降低我們程序的效率。所以提出了讀寫鎖。
讀寫鎖具有寫獨佔,讀共享,寫鎖優先級高的特性。
2.1.鎖的初始化
讀寫鎖可以靜態或動態的獲取,其中靜態獲取的方式如下:
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
其中 PTHREAD_RWLOCK_INITIALIZER 是 “pthread_rwlock.h” 中一段宏定義
動態獲取的函數原型如下:
int pthread_rwlock_init(pthread_rwlock_t *rw, pthread_rwlockattr_t *attr);
其中:
rw:讀寫鎖;
attr:設置鎖的屬性,如下:
PTHREAD_PROCESS_SHARED:允許可訪問用於分配讀寫鎖的內存的任何線程對讀寫鎖進行處理。即使該鎖是在由多個進程共享的內存
中分配的,也允許對其進行處理;
PTHREAD_PROCESS_PRIVATE:讀寫鎖只能由某些線程處理,這些線程與初始化該鎖的線程在同一進程中創建。如果不同進程的線程
嘗試對此類讀寫鎖進行處理,則其行爲是不確定的。缺省值爲 PTHREAD_PROCESS_PRIVATE;
返回值:成功返回0,錯誤返回具體錯誤代碼。
2.2鎖的獲取與釋放
相關函數原型如下:
獲取一個讀出鎖,如果對應的讀寫鎖已由某個寫入者持有,那就阻塞調用線程:
int pthread_rwlock_rdlock(pthread_rwlock_t *);
獲取一個寫入鎖,如果對應的讀寫鎖已由另一個寫入者持有,或者已由一個或多個讀出者持有,那就阻塞調用線程:
int pthread_rwlock_wrlock(pthread_rwlock_t *);
釋放一個讀出鎖或寫入鎖:
int pthread_rwlock_unlock(pthread_rwlock_t *);
下面兩個函數嘗試獲取一個讀出鎖或寫入鎖,但是如果該鎖不能馬上取得,那就返回一個EBUSY錯誤,而不是把調用線程投入睡眠:
int pthread_rwlock_tryrdlock(pthread_rwlock_t *);
int pthread_rwlock_trywrlock(pthread_rwlock_t *);
返回值:成功返回0,錯誤返回具體錯誤代碼。
2.3鎖的銷燬
函數原型如下:
int pthread_rwlock_destroy(pthread_rwlock_t *);
返回值:成功返回0,錯誤返回具體錯誤代碼。
2.4示例代碼
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <bits/pthreadtypes.h>
#define WORK_SIZE 1024
static pthread_rwlock_t rwlock;
char work_area[WORK_SIZE];
int time_to_exit;
void *thread_to_read_one(void *arg);
void *thread_to_read_two(void *arg);
void *thread_to_write_one(void *arg);
void *thread_to_write_two(void *arg);
void *thread_to_read_one(void *arg)
{
printf("thread read one try to get lock\n");
pthread_rwlock_rdlock(&rwlock);
while(strncmp("end",work_area,3) != 0){
printf("this is thread read one\n");
printf("the characters is %s\n",work_area);
pthread_rwlock_unlock(&rwlock);
sleep(2);
pthread_rwlock_rdlock(&rwlock);
while(work_area[0] == '\0'){
pthread_rwlock_unlock(&rwlock);
sleep(2);
pthread_rwlock_rdlock(&rwlock);
}
}
pthread_rwlock_unlock(&rwlock);
time_to_exit = 1;
pthread_exit(0);
}
void *thread_to_read_two(void *arg)
{
printf("thread read one try to get lock\n");
pthread_rwlock_rdlock(&rwlock);
while(strncmp("end",work_area,3) != 0){
printf("this is thread read two\n");
printf("the characters is %s\n",work_area);
pthread_rwlock_unlock(&rwlock);
sleep(5);
pthread_rwlock_rdlock(&rwlock);
while(work_area[0] == '\0'){
pthread_rwlock_unlock(&rwlock);
sleep(5);
pthread_rwlock_rdlock(&rwlock);
}
}
pthread_rwlock_unlock(&rwlock);
time_to_exit = 1;
pthread_exit(0);
}
void *thread_to_write_one(void *arg)
{
printf("this is write thread one try to get lock\n");
while(!time_to_exit){
pthread_rwlock_wrlock(&rwlock);
printf("this is write thread one\n,input some text,enter 'end' to finish\n");
fgets(work_area,WORK_SIZE,stdin);
pthread_rwlock_unlock(&rwlock);
sleep(15); // forget sleep,so write always
}
pthread_rwlock_unlock(&rwlock);
pthread_exit(0);
}
void *thread_to_write_two(void *arg)
{
sleep(10);
while(!time_to_exit){
pthread_rwlock_wrlock(&rwlock);
printf("this is write thread two\n input some text,enter 'end' to finish\n");
fgets(work_area,WORK_SIZE,stdin);
pthread_rwlock_unlock(&rwlock);
sleep(20);
}
pthread_rwlock_unlock(&rwlock);
pthread_exit(0);
}
int main(int argc,char *argv[])
{
int retval;
pthread_t a_thread,b_thread,c_thread,d_thread;
void *thread_result;
retval = pthread_rwlock_init(&rwlock,NULL);
if(retval != 0){
exit(1);
}
retval = pthread_create(&a_thread,NULL,thread_to_read_one,NULL);
if(retval != 0){
exit(1);
}
retval = pthread_create(&b_thread,NULL,thread_to_read_two,NULL);
if(retval != 0){
exit(1);
}
retval = pthread_create(&c_thread,NULL,thread_to_write_one,NULL);
if(retval != 0){
exit(1);
}
retval = pthread_create(&d_thread,NULL,thread_to_write_two,NULL);
if(retval != 0){
exit(1);
}
pthread_join(a_thread,NULL);
pthread_join(b_thread,NULL);
pthread_join(c_thread,NULL);
pthread_join(d_thread,NULL);
pthread_rwlock_destroy(&rwlock);
printf("main thread will exit\n");
return 0;
}
3.文件鎖
文件鎖是用於解決資源的共享使用的一種機制:當多個用戶需要共享一個文件時,Linux通常採用的方法是給文件上鎖,來避免共享的資源產生競爭的狀態。
文件鎖包括建議性鎖和強制性鎖:
建議性鎖:要求每個使用上鎖文件的進程都要檢查是否有鎖存在,並且尊重已有的鎖。在一般情況下,內核和系統都不使用建議性鎖,它們依靠程序員遵守這個規定。
強制性鎖:是由內核執行的鎖,當一個文件被上鎖進行寫入操作的時候,內核將阻止其他任何文件對其進行讀寫操作。採用強制性鎖對性能的影響很大,每次讀寫操作都必須檢查是否有鎖存在。
在Linux中,實現文件上鎖的函數有fcntl()和flock(),下面來分別做介紹。
3.1 fcntl實現文件鎖
函數原型如下:
int fcntl(int fd, int cmd, struct flock *lock);
其中:
fd:文件描述符
cmd:可選參數如下:
F_DUPFD:複製一個現存的描述符
F_GETFD:獲得fd的close-on-exec(執行時關閉)文件描述符標誌,若標誌未設置,則文件經過exec()函數之後仍保持打開狀態
F_SETFD:設置close-on-exec 標誌,該標誌由參數arg 的FD_CLOEXEC位決定
F_GETFL:得到open設置的標誌
F_SETFL :改變open設置的標誌
F_GETLK:根據lock參數值,決定是否可以上文件鎖
F_SETLK:設置lock參數值的文件鎖
flock :數據結構如下:
struct flock {
short l_type; /* 鎖類型: F_RDLCK(讀鎖), F_WRLCK(寫鎖), F_UNLCK (刪除鎖)*/
short l_whence; /* SEEK_SET(起始),SEEK_CUR(當前), SEEK_END(結尾) */
off_t l_start; /* 根據l_whence決定偏移量,可以爲負數 */
off_t l_len; /* 鎖定字節數,0 代表直到EOF */
pid_t l_pid; /* 阻止我們取得鎖的pid (F_GETLK only) */
};
示例代碼如下:
int fd = open(“./test.txt”, O_RDWR | O_CREAT, LOCKMODE);
struct flock fl;
fl.l_type = F_WRLCK; /* write lock */
fl.l_start = 0;
fl.l_whence = SEEK_SET;
fl.l_len = 0; //lock the whole file
fl.l_pid = getpid();
fcntl(fd, F_SETLK, &fl);
3.2 flock實現文件鎖
函數原型如下:
int flock(int fd, int operation);
其中:
fd:打開的文件描述符;
operation:參數可選值如下:
LOCK_SH:放置共享鎖;
LOCK_EX: 放置互斥鎖;
LOCK_UN:解鎖;
LOCK_NB:非阻塞鎖請求,可如上述三個參數聯合使用;
在默認情況下,如果另一進程已經持有了文件上的一個不兼容的鎖,那麼flock()會阻塞,如果設置成非阻塞模式,那麼將返回-1,errno設置成 EWOULDBLOCK。
任意數量的進程可以持有文件的共享鎖,但在同一時刻只能有一個進程擁有互斥鎖,一旦一個進程擁有互斥鎖,其他進程的共享鎖請求也會被拒絕。
示例代碼如下:
#include <time.h>
#include <fcntl.h>
#include <stdio.h>
#include <limits.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#define BUF_SIZE 1000
char *currTime(const char *fmt);
void errExit(char *msg)
{
perror(msg);
exit(1);
}
char * currTime(const char *format)
{
static char buf[BUF_SIZE];
time_t t;
size_t s;
struct tm *tm;
t = time(NULL);
tm = localtime(&t);
if (tm == NULL)
return NULL;
s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm);
return (s == 0) ? NULL : buf;
}
int main(int argc, char *argv[])
{
int fd, lock;
const char *lname;
if (argc < 3 || strcmp(argv[1], "--help") == 0 || strchr("sx", argv[2][0]) == NULL){
printf("%s file lock [sleep-time]\n"
" 'lock' is 's' (shared) or 'x' (exclusive)\n"
" optionally followed by 'n' (nonblocking)\n"
" 'sleep-time' specifies time to hold lock\n", argv[0]);
return 0;
}
lock = (argv[2][0] == 's') ? LOCK_SH : LOCK_EX;
if (argv[2][1] == 'n')
lock |= LOCK_NB;
fd = open(argv[1], O_RDONLY); /* Open file to be locked */
if (fd == -1)
errExit("open");
lname = (lock & LOCK_SH) ? "LOCK_SH" : "LOCK_EX";
printf("PID %ld: requesting %s at %s\n", (long) getpid(), lname,
currTime("%T"));
if (flock(fd, lock) == -1) {
if (errno == EWOULDBLOCK){
printf("PID %ld: already locked - bye!", (long) getpid());
return 1;
}else{
printf("flock (PID=%ld)", (long) getpid());
return 1;
}
}
printf("PID %ld: granted %s at %s\n", (long) getpid(), lname, currTime("%T"));
sleep((argc > 3) ? atoi(argv[3]) : 10);
printf("PID %ld: releasing %s at %s\n", (long) getpid(), lname, currTime("%T"));
if (flock(fd, LOCK_UN) == -1)
errExit("flock");
exit(EXIT_SUCCESS);
}
4.自旋鎖
自旋鎖是一種特殊的互斥鎖,當資源被枷鎖後,其他線程想要再次加鎖,此時該線程不會被阻塞睡眠而是陷入循環等待狀態(不能在做其它事情),循環檢查資源持有者是否已經釋放了資源,這樣做的好處是減少了線程從睡眠到喚醒的資源消耗,但會一直佔用CPU的資源。適用於資源的鎖被持有的時間短,而又不希望在線程的喚醒上花費太多資源的情況。
————————————————
版權聲明:本文爲CSDN博主「浪裏個浪の」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u010304442/article/details/90449716