Linux的線程鎖【轉】

(轉自: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

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