10分鐘搞定Linux多線程同步(互斥量,死鎖、讀寫鎖,條件變量,信號量,文件鎖)

在這裏插入圖片描述

1. 線程同步的一些概念

1.1 同步的概念

  所謂同步,即同時起步,不同的對象,對“同步”的理解方式略有不同。如,設備同步,是指在兩個設備間規定一個共同的時間參考;數據同步,是指讓兩個或多個數據庫內容保持一致,或者按需要部分保持一致。文件同步,是指讓兩個或多個文件夾裏的內容保持一致,等等。
  而編程中、通信中所說的同步與生活中大家印象中的同步概念略有差異。“同”字應是指協同、協助、互相配合。主旨在協同步調,按預定的先後次序運行。

1.2 什麼是線程同步

  同步即協同步調,按預定的先後次序運行。
  線程同步,指一個縣城發出某一功能調用時,在沒有得到結果之前,該調用不返回。同時其他線程爲保證數據一致性,不能調用該功能。
舉例1:銀行存款5000。櫃檯,折:取3000;提款機,卡:取3000.剩餘2000
舉例2:內存中100字節,線程T1欲填入全1,線程T2欲填入全0.但如果T1執行了50個字節失去cpu,T2執行,會將T1寫過的內容覆蓋。當T1再次獲得cpu繼續從失去cpu的位置向後寫入1,當執行結束,內存中的100字節,既不是全1,也不是全0。
  產生的現象叫做“與時間有關的錯誤”(time related)。爲了避免這種數據混亂,線程需要同步。
  “同步”的目的,是爲了避免數據混亂,解決與時間有關的錯誤,實際上,不僅線程間需要同步,進程間、信號間等等都需要同步機制。
  因此,所有“多個控制流,共同操作一個共享資源”的情況,都需要同步。

1.3 多線程出現數據混亂(數據競爭)的原因

  1. 資源共享(獨享資源則不會)
  2. 調度隨機(意味着數據訪問會出現競爭者)
  3. 線程間缺乏必要的同步機制

  以上三點中前兩點不能改變,想提高效率,傳遞數據,資源必須共享。只要共享資源,就一定會出現競爭。只要存在競爭關係,數據就很容易出現混亂。
  所以只能從第三點着手解決。使多個線程在訪問共享資源的時候出現互斥。

2. 互斥量mutex

  1. Linux提供一把互斥鎖mutex(也稱之爲互斥量)
  2. 每個線程在對資源操作前都嘗試先加鎖,成功加鎖才能操作,操作結束後解鎖。
  3. 資源還是共享的,線程間也還是競爭的,但通過鎖將資源的訪問變爲互斥操作,而後與時間有關的錯誤也不會在產生了。

在這裏插入圖片描述

但是應該注意:同一個時刻,只能有一個線程持有該鎖

  當A線程對某個全局變量加鎖訪問,B在訪問前嘗試加鎖,拿不到鎖,B阻塞。C線程不去加鎖,而直接訪問該全局變量,依然能夠訪問,但會出現數據混亂。

  所以,互斥鎖實質上是是操作系統提供的一把“建議鎖”(又稱“協同所”),建議程序中有多線程訪問共享資源的時候使用該機制,但是,並沒有強制限定。

2.1 mutex相關的函數和使用步驟

pthread_mutex_t 類型,其本質是一個結構體,爲簡化理解,應用時可忽略其實現細節,簡單當成整數看待。
pthread_mutex_t mutex:變量mutex只有兩種取值01

pthread_mutex_init		初始化	
pthread_mutex_destroy	摧毀	
pthread_mutex_lock		加鎖
pthread_mutex_unlock	解鎖

互斥量的使用步驟:

  1. 初始化
  2. 加鎖
  3. 執行邏輯——操作共享數據
  4. 解鎖

注意事項
加鎖需要最小粒度,不要一直佔用臨界區(加鎖到解鎖之間的執行邏輯即爲臨界區)

2.1.1 初始化鎖

功能:初始化一個互斥鎖(互斥量);–>初值可看做1

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
返回值:		若成功,返回0,否則,返回錯誤編號

或者
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

參數:

mutex 			傳出參數,互斥量——鎖
restruct 		只用於限制指針,告訴編譯器,所有修改該指針指向內存中內容的操作,只能通過本指針完成。不能通過除本指針以外的其他變量或指針修改。
attr 			互斥屬性。是一個傳入參數,通常傳NULL,選用默認屬性(線程間共享).

2.1.2 給共享資源加鎖解鎖

這些pthread_mutex_t *mutex參數都是init初始化的那個鎖。
返回值: 若成功,返回0,否則,返回錯誤編號

pthread_mutex_lock(pthread_mutex_t *mutex)
功能:
如果當前未鎖,成功,加鎖,可理解爲將mutex--(或-1)
如果當前已鎖,阻塞等待,鎖被打開之後,線程解除阻塞。
pthread_mutex_unlock(pthread_mutex_t *mutex)
功能:
解鎖。可理解爲將mtex++(或+1),同時將阻塞在該鎖上的所有線程全部喚醒
pthread_mutex_trylock(pthread_mutex_t *mutex)
功能:
沒有鎖上:當前線程會給這把鎖加鎖
如果鎖上了:不會阻塞,返回

lock與unlock

  • lock嘗試加鎖,如果加鎖不成功,線程阻塞,阻塞到持有該互斥量的其他線程解鎖爲止。
  • unlock主動解鎖,同時將阻塞到該鎖上所有線程全部喚醒,至於哪個線程先被喚醒取決於優先級,調度。默認:先阻塞、先喚醒。
    例如:T1 T2 T3 T4 使用一把mutex鎖,T1加鎖成功,其他線程均阻塞,直至T1解鎖,T1解鎖後,T2 T3 T4均被喚醒,並自動再次嘗試加鎖。
    可假想mutex鎖init成功初值爲1。lock功能是將mutex–。unlock將mutex++。

2.2.3 摧毀鎖

int pthread_mutex_destroy(pthread_mutex_t *mutex);
返回值:		若成功,返回0,否則,返回錯誤編號 

參數也是init初始化的那個鎖。

2.2 互斥量使用的例子

通過互斥量,兩個線程交替打印

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
 
//常量初始化鎖——mutex(這樣就不用init函數了),將其定義爲全局變量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 
int sum=0;
 
void *thr1(void *arg){
    while(1){
		//先上鎖
        pthread_mutex_lock(&mutex);//加鎖,當有線程已經加鎖的時候會阻塞
        //加鎖到解鎖這一段爲臨界區
        printf("hello");
        sleep(rand()%3);
        printf("world\n");
		//釋放鎖
        pthread_mutex_unlock(&mutex);
		sleep(rand()%3);
    }
}
 
void *thr2(void *arg){
    while(1){
        pthread_mutex_lock(&mutex);
        printf("HELLO");
        sleep(rand()%3);
        printf("WORLD\n");
        pthread_mutex_unlock(&mutex);
        sleep(rand()%3);
    }
}
  
int main(){
    pthread_t tid[2];
    pthread_create(&tid[0],NULL,thr1,NULL);
    pthread_create(&tid[1],NULL,thr2,NULL);
 
    pthread_join(tid[0],NULL);
    pthread_join(tid[1],NULL);
    return 0;
}

運行結果:
在這裏插入圖片描述
如果不加鎖:
在這裏插入圖片描述

2.3 pthread_mutex_trylock

pthread_mutex_unlock(pthread_mutex_t *mutex)

lock與trylock:

  • lock加鎖失敗會阻塞,等待鎖釋放。
  • trylock加鎖失敗直接返回錯誤號(如EBUSY),不阻塞。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
 
pthread_mutex_t mutex;
  
void *thr(void *arg){
    while(1){
        pthread_mutex_lock(&mutex);
        printf("hello world\n");
        sleep(30);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
  
int main(){
    pthread_mutex_init(&mutex,NULL);
    pthread_t tid;
    pthread_create(&tid,NULL,thr,NULL);
    sleep(1);
    while(1){
        int ret = pthread_mutex_trylock(&mutex);
		//加鎖失敗
        if(ret > 0){
            printf("ret = %d,srrmsg:%s\n",ret,strerror(ret));
        }
        sleep(1);
    }
    return 0;
} 

運行結果:
在這裏插入圖片描述返回值的錯誤碼是16,我們來看一下16代表什麼意思。
在這裏插入圖片描述
錯誤碼定義的地方:

/usr/include/asm-generic/errno-base.h
/usr/include/asm-generic/errno.h
/usr/include/errno.h

3. 死鎖

產生條件
在這裏插入圖片描述

  • 鎖了又鎖,自己加了鎖,自己又加了一把鎖。一般都是有分支或者寫代碼的時候忘了會出現這個問題

  • 交叉鎖(如下圖所示)——解決方案:1、每個線程申請鎖的順序要一致;2、如果申請到一把鎖,另一個申請不到,則釋放已有資源
    在這裏插入圖片描述


4. 讀寫鎖

讀共享,寫互斥,寫的優先級高。適合讀的線程多的場景
讀寫鎖仍然是一把鎖,有不同的狀態:未加鎖、讀鎖、寫鎖。

4.1 讀寫鎖特性

  1. 讀寫鎖是“寫模式加鎖”時,解鎖前,所有對該鎖加鎖的線程都會被阻塞。
  2. 讀寫鎖是“讀模式加鎖”時,如果線程以讀模式對其加鎖會成功,如果線程以寫模式加鎖會阻塞。

場景例子:
(1)線程A加讀鎖成功,又來了三個線程,做讀操作,可以加鎖成功【讀共享 - 並行處理】
(2)線程A加寫鎖成功,又來了三個線程,做讀操作,三個線程阻塞 【寫獨佔
(3)線程A加讀鎖成功,又來了B線程加寫鎖阻塞,又來了C線程加讀鎖阻塞【讀寫不能同時;寫的優先級高(寫鎖都阻塞了那麼之後的讀鎖肯定阻塞)】

4.2 讀寫鎖使用場景

普遍讀寫鎖在讀的線程居多的時候使用。
讀:並行
寫:串行

讀寫鎖練習場景:

  • 線程A加寫鎖成功,線程B請求讀鎖
    線程B阻塞
  • 線程A持有讀鎖,線程B請求寫鎖
    線程B阻塞
  • 線程A持有讀鎖,線程B請求讀鎖
    線程B加鎖成功
  • 線程A持有讀鎖,然後線程B請求寫鎖,然後線程C請求讀鎖
    B阻塞,C阻塞 - 寫的優先級高
    A解鎖,B線程加寫鎖成功
    B解鎖,C加讀鎖成功
  • 線程A持有寫鎖,然後線程B請求讀鎖,然後線程C請求寫鎖
    BC阻塞
    A解鎖,C加寫鎖成功,B繼續阻塞
    C解鎖,B加讀鎖

4.3 讀寫鎖主要操作函數

初始化讀寫鎖:

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
或者:
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

銷燬讀寫鎖:

pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

加讀鎖:

pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

阻塞:之前對這把鎖加的寫鎖的操作

嘗試加讀鎖:

pthread_rwlock_trydlock(pthread_rwlock_t *rwlock);
返回值:
加鎖成功		0
失敗			錯誤號

加寫鎖:

pthread_rwlock_wrlock(pthread_rwlock_t *rwlocl);

上一次加鎖寫鎖,還沒有解鎖的時候
上一次加讀鎖,沒解鎖

嘗試加寫鎖:

pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

解鎖:

pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

4.4 讀寫鎖例子

5個讀,3個寫

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
 
//初始化
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int beginnum = 1000;
 
void *thr_write(void *arg) {
    while(1){
		//寫鎖加鎖
        pthread_rwlock_wrlock(&rwlock);
        printf("---%s---self---%lu---beginnum---%d\n",__FUNCTION__,pthread_self(),++beginnum);
        usleep(2000);//模擬佔用時間
		//解鎖
        pthread_rwlock_unlock(&rwlock);
        usleep(4000);
    }
    return NULL;
}
 
void *thr_read(void *arg) {
    while(1){
		//讀鎖加鎖
        pthread_rwlock_rdlock(&rwlock);
        printf("---%s---self---%lu---beginnum---%d\n",__FUNCTION__,pthread_self(),beginnum);
        usleep(2000);//模擬佔用時間
		//解鎖
        pthread_rwlock_unlock(&rwlock);
        usleep(2000);
 
    }
    return NULL;
}
 
int main(){
	//創建5個讀鎖和3個寫鎖
    int n  =8,i = 0;
    pthread_t tid[8];//5-read ,3-write 
    for(i = 0; i < 5; i ++){
		//參數依次是線程地址、線程屬性、函數名、傳入的參數
        pthread_create(&tid[i],NULL,thr_read,NULL);
    }
    for(;i < 8; i ++){
		//參數依次是線程地址、線程屬性、函數名、傳入的參數
        pthread_create(&tid[i],NULL,thr_write,NULL);
    }
 
    for(i = 0; i < 8;i ++){
		//線程回收
        pthread_join(tid[i],NULL);
    }
    return 0;
}

執行結果:
在這裏插入圖片描述


5. 條件變量與消費者模式

條件變量概念是線程掛起直到共享數據的某些條件得到滿足。
引入條件變量原因:mutex會產生如下問題:
多個線程搶到鎖之後,發現並沒有資源,因此釋放鎖,然後繼續多線程搶鎖,然後釋放鎖,這戶造成資源的浪費。

條件變量一般與互斥量協同作用

  • 使用條件變量+互斥量
    互斥量: 保護一塊共享資源
    條件變量:引起阻塞
    生產者和消費者模型
    在這裏插入圖片描述

條件變量的兩個動作?

  • 條件不滿足,阻塞線程
  • 當條件滿足,通知阻塞的線程開始工作

條件變量的類型:pthread_cond_t;


在這裏插入圖片描述

5.1 主要函數

pthread_cond_init			初始化
pthread_cond_destroy		銷燬一個條件變量
pthread_cond_wait			阻塞等待一個條件變量
pthread_cond_timewait		限時等待一個條件變量
pthread_cond_signal			喚醒至少一個阻塞在條件變量上的線程
pthread_cond_broadcast		喚醒全部阻塞在條件變量上的線程

5.1.1 初始化

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
或者:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

5.1.2 銷燬一個條件變量

pthread_cond_destroy(pthread_cond_t *cond);

5.1.3 阻塞等待一個條件變量

pthread_cond_wait(
	pthread_cond_t *restrict cond,
	pthread_mutex_t *restrict mutex
);1)阻塞線程
(2)將已經上鎖的mutex解鎖
(3)該函數解除阻塞,會對互斥鎖加鎖

5.1.4 限時等待一個條件變量

pthread_cond_timewait(
	pthread_cond_t *restrict cond,
	pthread_mutex_t *restrict mutex,
	const struct timespec *restrict abstime
);

參看 man sem_timedwait函數,查看struct timespec結構體

struct timespec {
	time_t tv_sec;      /* Seconds */
	long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
};

tv_sec絕對時間,填寫的時候time(NULL)+600 ==>設置超時600s


5.1.5 喚醒至少一個阻塞在條件變量上的線程

pthread_cond_signal(pthread_cond_t *restrict cond);

5.1.6 喚醒全部阻塞在條件變量上的線程

pthread_cond_broadcast(pthread_cond_t *cond);

5.2 條件變量解決生產着消費者模式

用鏈表存儲數據
兩個線程,一個線程想把所有的內容都寫成0,另一個線程想把所有的內容都寫成1

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
 
int beginnum = 1000;
 
//初始化mutex
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
//初始化條件變量
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
 
typedef struct _ProdInfo{
	int num;
	struct _ProdInfo *next;
}ProdInfo;
 
ProdInfo *HEAD = NULL;
 
void *thr_producer(void *arg){
	//負責向鏈表添加數據
	while(1){
		ProdInfo *prod=(ProdInfo *)malloc(sizeof(ProdInfo));
		prod->num=beginnum++;
		//上鎖
		pthread_mutex_lock(&mutex);
		//add to list
		prod->next=HEAD;
		HEAD=prod;
		printf("--------%s--------seld=%lu------------%d\n",__FUNCTION__,pthread_self(), prod->num);
		pthread_mutex_unlock(&mutex);
		//發起通知
		pthread_cond_signal(&cond);
		sleep(rand()%4);
	}
	return NULL;
}
 
void *thr_customer(void *arg){
	ProdInfo *prod=NULL;
	while(1){
		//取鏈表的數據
		pthread_mutex_lock(&mutex);
		//判斷有沒有數據
		if(HEAD==NULL){
			//發送消息等待
			pthread_cond_wait(&cond, &mutex);
		}
		//此時鏈表非空
		prod=HEAD;
		HEAD=HEAD->next;
		printf("--------%s--------seld=%lu------------%d\n",__FUNCTION__,pthread_self(), prod->num);
		//解鎖
		pthread_mutex_unlock(&mutex);
		sleep(rand()%4);
		free(prod);
	}
	return NULL;
}

int main(){
	pthread_t tid[2];
	pthread_create(&tid[0], NULL, thr_producer, NULL);	
	pthread_create(&tid[1], NULL, thr_customer, NULL);
	
	pthread_join(tid[0], NULL);
	pthread_join(tid[1], NULL);
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
	return 0;
}

運行結果:

在這裏插入圖片描述
一個生產者多個消費者時

thr_customer中的

if(HEAD==NULL)

改成

while(HEAD==NULL)

即可。

6. 信號量

加強版的互斥鎖。適於多個資源多個線程訪問的情況。

6.1 主要函數

sem_init
sem_destroy
sem_wait
sem_trywait
sem_timedwait
sem_post

以上六個函數的返回值都是:
成功返回0,失敗返回-1,同時設置errno。(注意,他們沒有pthread前綴)
sem_t類型,本質仍是結構體。但應用期間可簡單看作爲整數,忽略實現細節(類似於使用文件描述符)。
sem_t sem;規定信號量sem不能小於0,。頭文件<semaphore.h>


6.1.1 初始化

int sem_init(sem_t *sem, int pshared, unsigned int value);
sem 		定義的信號量,傳出
pshared 	非0則代表進程信號量, 0代表線程信號量
value 		定義信號量的併發數(鑰匙的個數)

6.1.2 摧毀

int sem_destroy(sem_t *sem);

摧毀一個信號量

6.1.3 申請信號量,申請成功,則value–

int sem_wait(sem_t *sem);

當信號量爲0的時候,阻塞

6.1.4 釋放信號量 value++

int sem_post(sem_t *sem);

6.2 信號量基本操作

sem_wait:(類比pthread_mutex_lock

  1. 信號量大於0,對信號量--
  2. 信號量等於0,造成線程阻塞

sem_post:將信號量++,同時喚醒阻塞在信號量上的線程(類比pthread_mutex_unlock
但,由於sem_t的實現對用戶隱藏,所以所謂的++--操作只能通過函數來實現,而不能直接++--符號。

以上操作也成爲PV操作
在這裏插入圖片描述
信號量的初值,決定了佔用信號量的線程的個數。

6.3 信號量實現生產者消費者模式

在這裏插入圖片描述

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
 
//blank只有多少可以放生產者生產東西的地方,xfull是消費者可以消費的東西的數量
sem_t blank,xfull;
#define _SEM_CNT_  5
// 模擬餅筐
int queue[_SEM_CNT_];
int beginnum = 100;
 
void *thr_producter(void *arg) {
    int i = 0;
    while(1){
        //申請資源 blank--
        //看看能不能生產,還有沒有空間
        sem_wait(&blank);
        //打印函數名、線程名、數量
        printf("-----%s-----self--%lu----num----%d\n",__FUNCTION__,pthread_self(),beginnum);
        //生產數據
        queue[(i++)%_SEM_CNT_] = beginnum++;
        //xfull ++
        sem_post(&xfull);
        sleep(rand()%3);
    }
    return NULL;
}
 
void *thr_customer(void *arg) {
    int i = 0;
    int num = 0;
    while(1){
        //看看能不能消費
        sem_wait(&xfull);
        //通過取餘來
        num = queue[(i++)%_SEM_CNT_];
        printf("-----%s-----self--%lu----num----%d\n",__FUNCTION__,pthread_self(),num);
        //發送信號
        sem_post(&blank);
        sleep(rand()%3);
    }
    return NULL;
}
 
 
int main(){
    //線程,所有第二個參數是0, 如果是進程,則第二個參數是非0
    sem_init(&blank,0,_SEM_CNT_);
    //消費者一開始的初始化默認沒有產品
    sem_init(&xfull,0,0);
 
    pthread_t tid[2];
 
    //線程沒有設置屬性,所有第二個參數爲NULL
    pthread_create(&tid[0],NULL,thr_producter,NULL);
    pthread_create(&tid[1],NULL,thr_customer,NULL);
 
    pthread_join(tid[0],NULL);
    pthread_join(tid[1],NULL);
 
    sem_destroy(&blank);
    sem_destroy(&xfull);
    return 0;
}

運行結果:
在這裏插入圖片描述


6.4 信號量與互斥量的區別

No. 區別 信號量 互斥量
1 使用對象 線程和進程 線程
2 最值 非負整數 0或1
3 操作 PV操作可由不同線程完成 加鎖和解鎖必須由同一線程使用
4 應用 用於線程的同步 用於線程的互斥
  • 互斥:主要關注於資源訪問的唯一性和排他性。
  • 同步:主要關注於操作的順序,同步以互斥爲前提。

7. 文件鎖

藉助fcntl函數來實現鎖機制。操作文件的進程沒有獲得鎖時,可以打開,但無法執行read、write操作。

適合環境——當前系統中該進程只能起一個

實現原理——當一個進程打開了這個文件,另一個進程發現文件被打開了,就無法再打開這個文件了

文件鎖——讀共享,寫獨佔

7.1 fcntl函數

int fcntl(int fd, int cmd, ... /* arg */ );1		文件描述符
參2 	
		F_SETLK(struct flock *)		設置文件鎖(trylock)
		F_SETLKW(struct flock *)	設置文件鎖(lock) W-->wait
		F_GETTLK(struct flock *)	獲取文件鎖
參3
		 struct flock {
               ...
               short l_type;    鎖的類型:F_RDLCK、F_WRLCK、F_UNLCK
               short l_whence;  偏移位置:SEEK_SETSEEK_CURSEEK_END
               off_t l_start;   起始偏移:0
               off_t l_len;    	長度:0表示整個文件加鎖
               pid_t l_pid;     持有該所的進程ID:(F_GETLK only)
               ...
           };

以上可以man fcntl查看
在這裏插入圖片描述

7.2 文件鎖舉例

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
 
#define _FILE_NAME_ "/home/itheima/temp.lock"
 
int main() {
    int fd = open(_FILE_NAME_,O_RDWR|O_CREAT,0666);
    if(fd < 0){
        //文件打開失敗
        perror("open err");
        return -1;
    }
    struct flock lk;
    lk.l_type = F_WRLCK;
    lk.l_whence =SEEK_SET ;
    lk.l_start = 0;
    lk.l_len  =0;
 
    if(fcntl(fd,F_SETLK,&lk) < 0){
        perror("get lock err");
        exit(1);
    }
    // 核心邏輯
    while(1){
        printf("I am alive!\n");
        sleep(1);
    }
    return 0;
}

在這裏插入圖片描述
開啓另外一個終端
在這裏插入圖片描述

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