這裏寫目錄標題
1、線程概述
在許多經典的操作系統教科書中,總是把進程定義爲程序的執行實例,它並不執行 什麼,只是維護應用程序所需的各種資源,而線程則是真正的執行實體。 所以, 線程是輕量級的進程(LWP:lightweightprocess),在Linux環境下線程的本質 仍是進程。 爲了讓進程完成一定的工作,進程必須至少包含一個線程。
進程是系統分配資源的基本單位。
線程是CPU調度的基本單位。
線程是輕量級的進程(LWP:light weight process)
進程,直觀點說,保存在硬盤上的程序運行以後,會在內存空間裏形成一個獨立的內存體,這個內存體有自己的地址空間,有自己的堆,上級掛靠單位是操作系統。操作系統會以進程爲單位,分配系統資源,所以我們也說,進程是系統分配資源的最小單位。 線程存在於進程當中(進程可以認爲是線程的容器),是CPU調度執行的最小單位。說通俗點,線程就是幹活的。 進程是具有一定獨立功能的程序關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位。 線程是進程的一個實體,是 CPU 調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。 如果說進程是一個資源管家,負責從主人那裏要資源的話,那麼線程就是幹活的苦力。一個管家必須完成一項工作,就需要最少一個苦力,也就是說,一個進程最少包含一個線程,也可以包含多個線程。苦力要幹活,就需要依託於管家,所以說一個線程,必須屬於某一個進程。 進程有自己的地址空間,線程使用進程的地址空間,也就是說,進程裏的資源,線程都是有權訪問的,比如說堆啊,棧啊,靜態存儲區什麼的。
進程是操作系統分配資源的最小單位
線程是操作系統調度的最小單位
線程的特點:
- 線程是輕量級進程(light-weight process),也有PCB,創建線程使用的底層函數和進程一樣,都是clone
- 從內核裏看進程和線程是一樣的,都有各自不同的PCB.
- 進程可以蛻變成線程
- 在linux下,線程最是小的執行單位;進程是最小的分配資源單位
查看指定進程的LWP號: ps -Lf pid 實際上,無論是創建進程的fork,還是創建線程的pthread_create,底層實現都是調用同一個內核函數 clone 。 Ø 如果複製對方的地址空間,那麼就產出一個“進程”; Ø 如果共享對方的地址空間,就產生一個“線程”。 Linux內核是不區分進程和線程的, 只在用戶層面上進行區分。所以,線程所有操作函數 pthread* 是庫函數,而非系統調用。
線程共享資源
- 文件描述符表
- 每種信號的處理方式
- 當前工作目錄
- 用戶ID和組ID 內存地址空間 (.text/.data/.bss/heap/共享庫)
線程非共享資源
- 線程id
- 處理器現場和棧指針(內核棧)
- 獨立的棧空間(用戶空間棧)
- errno變量
- 信號屏蔽字
- 調度優先級
線程的優缺點
優點: Ø 提高程序併發性 Ø 開銷小 Ø 數據通信、共享數據方便
缺點: Ø 庫函數,不穩定 Ø 調試、編寫困難、gdb不支持 Ø 對信號支持不好 優點相對突出,缺點均不是硬傷。Linux下由於實現方法導致進程、線程差別不是很大。
2、線程號
進程號用 pid_t 數據類型表示,是一個非負整數。線程號則用 pthread_t 數據類型來表示,Linux 使用無符號長整數表示。 有的系統在實現pthread_t 的時候,用一個結構體來表示,所以在可移植的操作系統實現不能把它做爲整數處理。
獲取線程號pthread_self函數:
#include <pthread.h>
pthread_t pthread_self(void);
功能:
獲取線程號。
參數:
無
返回值:
調用線程的線程 ID
gcc xxx.c -o a.out -pthread
1、線程的創建
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg );
功能:
創建一個線程
參數:
thread:線程標識符地址。
attr:線程屬性結構體地址,通常設置爲 NULL。
start_routine:線程函數的入口地址。
arg:傳給線程函數的參數。
返回值:
成功:0
失敗:非 0
在一個線程中調用pthread_create()創建新的線程後,當前線程從pthread_create()返回繼續往下執行,而新的線程所執行的代碼由我們傳給pthread_create的函數指針startroutine決定
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>
void *pthread_func01(void *arg)
{
//線程的核心代碼
while(1)
{
printf("%s\n",(char *)arg);
sleep(1);
}
}
int main()
{
//創建一個線程ID
pthread_t tid1;
//創建一個線程
pthread_create(&tid1,NULL, pthread_func01 ,"第一個線程");
//創建一個線程ID
pthread_t tid2;
//創建一個線程
pthread_create(&tid2,NULL, pthread_func01 ,"第二個線程");
while(1);
return 0;
}
運行結果:
2、回收線程資源
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能:
等待線程結束(此函數會阻塞),並回收線程資源,類似進程的 wait() 函數。
如果線程已經結束,那麼該函數會立即返回。
參數:
thread:被等待的線程號。
retval:用來存儲線程退出狀態的指針的地址
返回值:
成功:0
失敗:非 0
案例:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>
void *pthread_func01(void *arg)
{
//線程的核心代碼
int i=0;
for(i=0;i<5;i++)
{
printf("%s--->i=%d\n",(char *)arg,i);
sleep(1);
}
return NULL;
}
int main()
{
//創建一個線程ID
pthread_t tid1;
//創建一個線程
pthread_create(&tid1,NULL, pthread_func01 ,"第一個線程");
//線程等待pthread_join
printf("等待線程\n");
pthread_join(tid1, NULL);//阻塞 直到線程結束
printf("線程已經退出\n");
return 0;
}
運行結果:
案例:獲取線程的返回值(瞭解)
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>
void *pthread_func01(void *arg)
{
//線程的核心代碼
int i=0;
for(i=0;i<5;i++)
{
printf("%s--->i=%d\n",(char *)arg,i);
sleep(1);
}
return (void *)5;
}
int main()
{
//創建一個線程ID
pthread_t tid1;
//創建一個線程
pthread_create(&tid1,NULL, pthread_func01 ,"第一個線程");
//線程等待pthread_join
int ret;
pthread_join(tid1, (void **)&ret);//阻塞 直到線程結束
printf("ret = %d\n", ret);
return 0;
}
運行結果:
3、線程分離pthread_detach 不阻塞
int pthread_detach(pthread_t thread);
功能:
使調用線程與當前進程分離,分離後不代表此線程不依賴與當前進程,線程分離的目的是將線程資源的回收工作交由系統自動來完成,也就是說當被分離的線程結束之後,系統會自動回收它的資源。所以,此函數不會阻塞
參數:
thread:線程號。
返回值:
成功:0
失敗:非0
案例:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>
void *pthread_func01(void *arg)
{
//線程的核心代碼
int i=0;
for(i=0;i<5;i++)
{
printf("%s--->i=%d\n",(char *)arg,i);
sleep(1);
}
return NULL;
}
int main()
{
int i=0;
for(i=0;i<5;i++)
{
char buf[128]="";
sprintf(buf,"第%d個線程",i+1);
//創建一個線程ID
pthread_t tid1;
//創建一個線程
pthread_create(&tid1,NULL, pthread_func01 ,buf);
//線程等待pthread_detach
pthread_detach(tid1);//不阻塞
}
getchar();
return 0;
}
運行結果:
爲啥全是“第5個線程” pthread_create不阻塞 只是建立關係 啥時候調用線程函數是cpu決定
案例:創建多線程
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>
void *pthread_func01(void *arg)
{
//線程的核心代碼
int i=0;
for(i=0;i<5;i++)
{
printf("%u--->i=%d\n",pthread_self(),i);
sleep(1);
}
return NULL;
}
int main()
{
int i=0;
for(i=0;i<5;i++)
{
//創建一個線程ID
pthread_t tid;
//創建一個線程
pthread_create(&tid,NULL, pthread_func01 ,NULL);
//線程等待pthread_detach
pthread_detach(tid);//不阻塞
}
getchar();
return 0;
}
3、線程的退出
進程:
main函數中return結束 (自然死亡)
exit或_exit結束 (自殺)
kill 結束(他殺)
線程:
線程函數return (自然死亡)
pthread_exit (自殺)
pthread_cancel (他殺)
3.1、pthread_exit函數(自殺)
#include <pthread.h>
void pthread_exit(void *retval);
功能:
退出調用線程。一個進程中的多個線程是共享該進程的數據段,
因此,通常線程退出後所佔用的資源並不會釋放。
參數:
retval:存儲線程退出狀態的指針。
返回值:無
案例:
#include<stdio.h>
#include<pthread.h>
void* deal_fun(void *arg)
{
int i=0;
for(i=0;i<10;i++)
{
printf("線程運行中i = %d\n",i);
if(i==5)
pthread_exit(NULL);//結束線程 自殺
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, deal_fun , NULL);
pthread_join(tid,NULL);
return 0;
}
運行結果:
3.2、pthread_cancel(線程的取消)
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:
殺死(取消)線程
參數:
thread : 目標線程ID。
返回值:
成功:0
失敗:出錯編號
注意:線程的取消並不是實時的,而又一定的延時。
需要等待線程到達某個取消點(檢查點)
注意:線程的取消並不是實時的,而又一定的延時。需要等待線程到達某個取消點 (檢查點)。 類似於玩遊戲存檔,必須到達指定的場所(存檔點,如:客棧、倉 庫、城裏等)才能存儲進度。 殺死線程也不是立刻就能完成,必須要到達取消點。 取消點:是線程檢查是否被取消,並按請求進行動作的一個位置。通常是一些系統 調用creat,open,pause,close,read,write…執行命令man7pthreads可以 查看具備這些取消點的系統調用列表。 可粗略認爲一個系統調用(進入內核)即爲 一個取消點。
案例:
#include<stdio.h>
#include<pthread.h>
void* deal_fun(void *arg)
{
int i=0;
for(i=0;i<10;i++)
{
printf("線程運行中i = %d\n",i);
sleep(1);//取消點
}
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, deal_fun , NULL);
//3秒過後 取消tid線程
sleep(3);
pthread_cancel(tid);
pthread_join(tid,NULL);
return 0;
}
運行結果:
3.2.1線程取消分析
線程的取消狀態:能不能取消
是否接受其他線程的取消信號 pthread_setcancelstate 設置取消狀態
int pthread_setcancelstate(int state, int *oldstate);
//PTHREAD_CANCEL_ENABLE 可以取消
// PTHREAD_CANCEL_DISABLE 不允許取消
線程的取消點:立即取消 還是 到達取消點取消
線程是否立即取消(默認遇到取消點 才取消)pthread_setcanceltype 設置取消類型
int pthread_setcanceltype(int type, int *oldtype);
//PTHREAD_CANCEL_DEFERRED 取消點取消
//PTHREAD_CANCEL_ASYNCHRONOUS 立即取消
案例:
#include<stdio.h>
#include<pthread.h>
void* deal_fun(void *arg)
{
//設置取消狀態(能不能取消)
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);//能取消
//設置取消類型(在何時取消?)
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);//立即取消
int i=0;
for(i=0;i<10;i++)
{
printf("線程運行中i = %d\n",i);
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, deal_fun , NULL);
//3秒過後 取消tid線程
sleep(3);
pthread_cancel(tid);
pthread_join(tid,NULL);
return 0;
}
運行結果:
4、註冊線程清理函數
線程和進程一樣,線程也可以註冊它退出時候 調用的函數,這樣的函數 就叫線程清理函數
4.1、註冊清理函數:pthread_cleanup_push
void pthread_cleanup_push(void (*routine)(void *),void *arg);
//功能:將清理函數壓棧,註冊清理函數
//參數:
//routine:線程清理函數
//arg:傳遞給清理函數的參數
4.2、彈出清理函數:pthread_cleanup_pop
void pthread_cleanup_pop(int execute);
//功能:清理函數彈棧 (執行)
//參數:
//execute 線程清理函數執行標誌位
非0:彈出清理函數 執行清理函數
0:彈出清理函數 不執行清理函數
4.3、當線程執行以下情況會調用清理函數
1、調用pthread_exit 退出線程 執行清理函數
2、pthread_cancel 響應其他線程的取消請求 執行清理函數
3、用非0 的方式調用pthread_cleanup_pop 執行清理函數
注意:不管pthread_cleanup_pop 能否被執行 必須成對出現
案例:
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
void clean_fun(void *arg)//arg = str
{
char *str = (char *)arg;
if(str != NULL)
{
printf("堆區空間釋放了2\n");
free(str);
str =NULL;
}
return;
}
void* deal_fun(void *arg)
{
//從堆區申請空間
char *str=(char *)calloc(1,32);
strcpy(str, "hello pthread");
//註冊清理函數
pthread_cleanup_push(clean_fun, str);
printf("%s\n", str);
sleep(2);
pthread_exit(NULL);
//釋放空間
if(str != NULL)
{
printf("堆區空間釋放了1\n");
free(str);
str =NULL;
}
//彈出清理函數(push pop一定要成對出現)
pthread_cleanup_pop(1);
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, deal_fun , NULL);
pthread_join(tid,NULL);
return 0;
}
運行結果:
5、線程的屬性
線程都是採用線程的默認屬性,默認屬性已經可以解決絕大多數開發時遇到的問題。
可以通過設置線程棧的大小來降低內存的使用,增加最大線程個數
typedef struct
{
int etachstate; //線程的分離狀態
int schedpolicy; //線程調度策略
struct sched_param schedparam; //線程的調度參數
int inheritsched; //線程的繼承性
int scope; //線程的作用域
size_t guardsize; //線程棧末尾的警戒緩衝區大小
int stackaddr_set; //線程的棧設置
void* stackaddr; //線程棧的位置
size_t stacksize; //線程棧的大小
} pthread_attr_t;
主要結構體成員:
1. 線程分離狀態
2. 線程棧大小(默認平均分配)
3. 線程棧警戒緩衝區大小(位於棧末尾)
4. 線程棧最低地址 屬性值不能直接設置,須使用相關函數進行操作,
初始化的函數爲pthread_attr_init,這個函數必須在pthread_create函數之前調用。之後須用pthread_attr_destroy函數來釋放資源。
線程屬性主要包括如下屬性:
作用域(scope)、棧尺寸(stack size)、棧地址(stack address)、優先級(priority)、
分離的狀態(detached state)、調度策略和參數(scheduling policy and parameters)。
默認的屬性爲非綁定、非分離、缺省的堆棧、與父進程同樣級別的優先級.
線程屬性初始化
注意:應先初始化線程屬性,再pthread_create創建線程
初始化線程屬性函數:
int pthread_attr_init(pthread_attr_t *attr);
函數返回值:
成功:0;失敗:錯誤號
銷燬線程屬性所佔用的資源函數: int pthread_attr_destroy(pthread_attr_t *attr); 函數返回值:
成功:0;失敗:錯誤號
線程分離狀態的函數: 設置線程屬性,分離or非分離
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
獲取程屬性,分離or非分離
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstat);
參數:
attr:已初始化的線程屬性
detachstate:
分離狀態 u PTHREAD_CREATE_DETACHED(分離線程) u
PTHREAD_CREATE_JOINABLE(非分離線程)
案例:通過線程屬性 設置 線程分離
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
void* deal_fun(void *arg)
{
int i=0;
for(i=0;i<3;i++)
{
printf("線程執行中i=%d\n", i);
sleep(1);
}
return NULL;
}
int main()
{
//通過設置線程屬性 完成線程分離
pthread_attr_t attr;
//初始化線程屬性
pthread_attr_init(&attr);
//設置線程屬性 分離
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//pthread_create創建線程 其實僅僅是建立關係 不會等待線程執行
//但是如果線程函數執行時間特點短 還沒有來得及執行pthread_detach 線程就已經結束
pthread_t tid;
pthread_create(&tid, &attr, deal_fun , NULL);
//pthread_detach(tid);//線程分離
getchar();
//釋放線程屬性
pthread_attr_destroy(&attr);
return 0;
}
運行結果:
6、線程間的同步互斥
1、同步互斥的概述
互斥:是指散步在不同任務之間的若干程序片斷,當某個任務運行其中一個程序片段時,其它任務就不能運行它們之中的任一程序片段,只能等到該任務運行完這個程序片段後纔可以運行。最基本的場景就是:一個公共資源同一時刻只能被一個進程或線程使用,多個進程或線程不能同時使用公共資源
總結:一個公共資源同一時刻只能被一個進程或線程使用。(無執行順序)
同步:一個公共資源同一時刻只能被一個進程或線程使用。(規定的了執行順序)
同步是有序的互斥。
案例:
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
void myPrintf(char *str)
{
while(*str != 0)
{
printf("%c",*str);
fflush(stdout);
str++;
sleep(1);
}
}
void* deal_fun(void *arg)
{
myPrintf(arg);
return NULL;
}
int main()
{
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1, NULL, deal_fun, "hello");
pthread_create(&tid2, NULL, deal_fun, "world");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}
運行結果:
2、互斥鎖
互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問,互斥鎖只有兩種狀態,即加鎖( lock )和解鎖( unlock )
互斥:只需要一把鎖
互斥鎖的操作流程如下:
1)在訪問共享資源臨界區域前,對互斥鎖進行加鎖。
2)在訪問完成後釋放互斥鎖上的鎖。
3)對互斥鎖進行加鎖後,任何其他試圖再次對互斥鎖加鎖的線程將會被阻塞,直到鎖被釋放。
2.1、初始化一把鎖pthread_mutex_init
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *attr);
功能:
初始化一個互斥鎖。
參數:
mutex:互斥鎖地址。類型是 pthread_mutex_t 。
attr:設置互斥量的屬性,通常可採用默認屬性,即可將 attr 設爲 NULL。。
返回值:
成功:0,成功申請的鎖默認是打開的。
失敗:非 0 錯誤碼
靜態鎖的創建:
可以使用宏 PTHREAD_MUTEX_INITIALIZER 靜態初始化互斥鎖,
比如:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
這種方法等價於使用 NULL 指定的 attr 參數調用 pthread_mutex_init()
來完成動態初始化,不同之處在於 PTHREAD_MUTEX_INITIALIZER 宏不進行錯誤檢查
2.2、銷燬互斥鎖
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
銷燬指定的一個互斥鎖。互斥鎖在使用完畢後,必須要對互斥鎖進行銷燬,以釋放資源。
參數:
mutex:互斥鎖地址。
返回值:
成功:0
失敗:非 0 錯誤碼
2.3、申請上鎖
申請上鎖
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
對互斥鎖上鎖,若互斥鎖已經上鎖,則調用者阻塞,直到互斥鎖解鎖後再上鎖。
參數:
mutex:互斥鎖地址。
返回值:
成功:0
失敗:非 0 錯誤碼
int pthread_mutex_trylock(pthread_mutex_t *mutex);
調用該函數時,若互斥鎖未加鎖,則上鎖,返回 0;
若互斥鎖已加鎖,則函數直接返回失敗,即 EBUSY
2.4、解鎖
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
對指定的互斥鎖解鎖。
參數:
mutex:互斥鎖地址。
返回值:
成功:0
失敗:非0錯誤碼
案例:
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
//定義一把鎖
pthread_mutex_t mutex;
void myPrintf(char *str)
{
while(*str != 0)
{
printf("%c",*str);
fflush(stdout);
str++;
sleep(1);
}
}
void* deal_fun01(void *arg)
{
//上鎖 pthread_mutex_lock
pthread_mutex_lock(&mutex);
myPrintf(arg);
//解鎖 pthread_mutex_unlock
pthread_mutex_unlock(&mutex);
return NULL;
}
void* deal_fun02(void *arg)
{
//上鎖 pthread_mutex_lock
pthread_mutex_lock(&mutex);
myPrintf(arg);
//解鎖 pthread_mutex_unlock
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
//初始化鎖
pthread_mutex_init(&mutex,NULL);
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1, NULL, deal_fun01, "hello");
pthread_create(&tid2, NULL, deal_fun02, "world");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
//釋放鎖的資源
pthread_mutex_destroy(&mutex);
return 0;
}
運行結果:
3、死鎖
互斥條件 某資源只能被一個進程使用,其他進程請求該資源時,只能等待,直到 資源使用完畢後釋放資源。 請求和保持條件 程序已經保持了至少一個資源,但是 又提出了新要求,而這個資源被其他進程佔用,自己佔用資源卻保持不放。 不可 搶佔條件 進程已獲得的資源沒有使用完,不能被搶佔。 循環等待條件 必然存在 一個循環鏈。4)處理死鎖的思路 預防死鎖 破壞死鎖的四個必要條件中的一個 或多個來預防死鎖。 避免死鎖 和預防死鎖的區別就是,在資源動態分配過程中, 用某種方式防止系統進入不安全的狀態。 檢測死鎖 運行時出現死鎖,能及時發現 死鎖,把程序解脫出來 解除死鎖 發生死鎖後,解脫進程,通常撤銷進程,回收資 源,再分配給正處於阻塞狀態的進程。5)預防死鎖的方法 破壞請求和保持條件 協議1: 所有進程開始前,必須一次性地申請所需的所有資源,這樣運行期間就 不會再提出資源要求,破壞了請求條件,即使有一種資源不能滿足需求,也不會給 它分配正在空閒的資源,這樣它就沒有資源,就破壞了保持條件,從而預防死鎖的 發生。 協議2: 允許一個進程只獲得初期的資源就開始運行,然後再把運行完的 資源釋放出來。然後再請求新的資源。 破壞不可搶佔條件 當一個已經保持了某種 不可搶佔資源的進程,提出新資源請求不能被滿足時,它必須釋放已經保持的所有 資源,以後需要時再重新申請。 破壞循環等待條件 對系統中的所有資源類型進行 線性排序,然後規定每個進程必須按序列號遞增的順序請求資源。假如進程請求到 了一些序列號較高的資源,然後有請求一個序列較低的資源時,必須先釋放相同和 更高序號的資源後才能申請低序號的資源。多個同類資源必須一起請求。
4、讀寫鎖
能夠允許多個讀出,只允許一個寫入。
讀不能寫,寫不能讀;同一時刻,寫只能一個線程,讀可以多個線程
讀寫鎖的特點:
1)如果有其它線程讀數據,則允許其它線程執行讀操作,但不允許寫操作。
2)如果有其它線程寫數據,則其它線程都不允許讀、寫操作
讀寫鎖分爲讀鎖和寫鎖,規則:
1)如果某線程申請了讀鎖,其它線程可以再申請讀鎖,但不能申請寫鎖。
2)如果某線程申請了寫鎖,其它線程不能申請讀鎖,也不能申請寫鎖
4.1、初始化讀寫鎖
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *rwlock,
const pthread_rwlockattr_t *attr);
功能:
用來初始化 rwlock 所指向的讀寫鎖。
參數:
rwlock:指向要初始化的讀寫鎖指針。
attr:讀寫鎖的屬性指針。
如果 attr 爲 NULL 則會使用默認的屬性初始化讀寫鎖,
否則使用指定的 attr 初始化讀寫鎖。。
返回值:
成功:0,讀寫鎖的狀態將成爲已初始化和已解鎖。
失敗:非 0 錯誤碼
可以使用宏 PTHREAD_RWLOCK_INITIALIZER 靜態初始化讀寫鎖,
比如:pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER;
這種方法等價於使用 NULL
指定的 attr 參數調用 pthread_rwlock_init()
來完成動態初始化,不同之處在於PTHREAD_RWLOCK_INITIALIZER 宏不進行錯誤檢查
4.2、釋放讀寫鎖
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
功能:
用於銷燬一個讀寫鎖,並釋放所有相關聯的資源
(所謂的所有指的是由 pthread_rwlock_init() 自動申請的資源) 。
參數:
rwlock:讀寫鎖指針。
返回值:
成功:0
失敗:非 0 錯誤碼
4.3、申請讀鎖
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
功能:
以阻塞方式在讀寫鎖上獲取讀鎖(讀鎖定)。
如果沒有寫者持有該鎖,並且沒有寫者阻塞在該鎖上,則調用線程會獲取讀鎖。
如果調用線程未獲取讀鎖,則它將阻塞直到它獲取了該鎖。
一個線程可以在一個讀寫鎖上多次執行讀鎖定。
線程可以成功調用 pthread_rwlock_rdlock() 函數 n 次,
但是之後該線程必須調用 pthread_rwlock_unlock() 函數 n 次才能解除鎖定。
參數:
rwlock:讀寫鎖指針。
返回值:
成功:0
失敗:非 0 錯誤碼
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
用於嘗試以非阻塞的方式來在讀寫鎖上獲取讀鎖。
如果有任何的寫者持有該鎖或有寫者阻塞在該讀寫鎖上,則立即失敗返回
4.4、申請寫鎖
#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
功能:
在讀寫鎖上獲取寫鎖(寫鎖定)。
如果沒有寫者持有該鎖,並且沒有寫者讀者持有該鎖,則調用線程會獲取寫鎖。
如果調用線程未獲取寫鎖,則它將阻塞直到它獲取了該鎖。
參數:
rwlock:讀寫鎖指針。
返回值:
成功:0
失敗:非 0 錯誤碼
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
用於嘗試以非阻塞的方式來在讀寫鎖上獲取寫鎖。
如果有任何的讀者或寫者持有該鎖,則立即失敗返回
4.5、釋放讀寫鎖
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
功能:
無論是讀鎖或寫鎖,都可以通過此函數解鎖。
參數:
rwlock:讀寫鎖指針。
返回值:
成功:0
失敗:非 0 錯誤碼
案例:
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
//定義一把讀寫鎖
pthread_rwlock_t rwlock;
//模擬公共資源
int num = 0;
void* read_fun(void *arg)
{
while(1)
{
//上讀鎖
pthread_rwlock_rdlock(&rwlock);
printf("%lu線程讀取num=%d\n",pthread_self(), num);
//解讀鎖
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
return NULL;
}
void* write_fun(void *arg)
{
while(1)
{
//上寫鎖
pthread_rwlock_wrlock(&rwlock);
num++;
printf("%lu線程寫入num=%d\n",pthread_self(), num);
//解寫鎖
pthread_rwlock_unlock(&rwlock);
sleep(3);
}
return NULL;
}
int main()
{
//初始化一把讀寫鎖
pthread_rwlock_init(&rwlock,NULL);
pthread_t tid1_r;
pthread_t tid2_r;
pthread_t tid3_w;
pthread_create(&tid1_r, NULL, read_fun, NULL);
pthread_create(&tid2_r, NULL, read_fun, NULL);
pthread_create(&tid3_w, NULL, write_fun, NULL);
pthread_join(tid1_r,NULL);
pthread_join(tid2_r,NULL);
pthread_join(tid3_w,NULL);
//銷燬讀寫鎖
pthread_rwlock_destroy(&rwlock);
return 0;
}
5、條件變量
與互斥鎖不同,條件變量是用來等待而不是用來上鎖的,條件變量本身不是鎖! 條件變量用來自動阻塞一個線程,直到某特殊情況發生爲止。通常條件變量和互斥鎖同時使用。 條件變量的兩個動作: 條件不滿, 阻塞線程 當條件滿足, 通知阻塞的線程開始工作 條件變量的類型: pthread_cond_t
5.1、條件變量初始化pthread_cond_init
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond,
const pthread_condattr_t *attr);
功能:
初始化一個條件變量
參數:
cond:指向要初始化的條件變量指針。
attr:條件變量屬性,通常爲默認值,傳NULL即
返回值:
成功:0
失敗:非0錯誤號
也可以使用靜態初始化的方法,初始化條件變量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
5.2、釋放條件變量pthread_cond_destroy
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
功能:
銷燬一個條件變量
參數:
cond:指向要初始化的條件變量指針
返回值:
成功:0
失敗:非0錯誤號
5.3、等待條件pthread_cond_wait
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex);
功能:
阻塞等待一個條件變量
a) 阻塞等待條件變量cond(參1)滿足
b) 釋放已掌握的互斥鎖(解鎖互斥量)相當於pthread_mutex_unlock(&mutex);
a) b) 兩步爲一個原子操作。
c) 當被喚醒,pthread_cond_wait函數返回時,解除阻塞並重新申請獲取互斥鎖pthread_mutex_lock(&mutex);
參數:
cond:指向要初始化的條件變量指針
mutex:互斥鎖
返回值:
成功:0
失敗:非0錯誤號
int pthread_cond_timedwait(pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct *abstime);
功能:
限時等待一個條件變量
參數:
cond:指向要初始化的條件變量指針
mutex:互斥鎖
abstime:絕對時間
返回值:
成功:0
失敗:非0錯誤號
5.4、喚醒等待在條件變量上的線程
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
功能:
喚醒至少一個阻塞在條件變量上的線程
參數
cond:指向要初始化的條件變量指
返回值
成功:0
失敗:非0錯誤號
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:
喚醒全部阻塞在條件變量上的線程
參數:
cond:指向要初始化的條件變量指針
返回值:
成功:0
失敗:非0錯誤號
int pthreadcondbroadcast(pthreadcondt *cond);
功能: 給阻塞在條件變量上的所有線程發送信號 參數: cond 條件變量的地址
案例:生產者、消費者
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include<time.h>
//定義一個互斥鎖
pthread_mutex_t mutex;
//定義一個條件變量
pthread_cond_t cond;
//定義一個倉庫num
int num = 0;
void* producer(void *arg)
{
//不停的生產
while(1)
{
//模擬一個生產時間
sleep(rand()%3);//0~2秒
//將數據放入倉庫(num++)
pthread_mutex_lock(&mutex);
num++;
printf("%s 在生產 當前num=%d\n", (char *)arg,num);
//通知其他消費線程 條件變量 滿足
pthread_cond_broadcast(&cond);
//解鎖
pthread_mutex_unlock(&mutex);
}
}
void* consumer(void *arg)
{
while(1)
{
//模擬去倉庫取產品
//上鎖
pthread_mutex_lock(&mutex);
//先判斷num==0
while(num == 0)//防止 上鎖成功 結果數據num還是0
pthread_cond_wait(&cond,&mutex);//阻塞->等待條件同時解鎖->條件滿足同時上鎖
num--;
printf("%s消費了一個產品 num剩餘數量num=%d\n",(char *)arg,num);
//解鎖
pthread_mutex_unlock(&mutex);
//模擬一個消費時間
sleep(rand()%3);
}
}
int main()
{
//設置隨機數種子
srand(time(NULL));
//初始化互斥鎖
pthread_mutex_init(&mutex,NULL);
//初始化條件變量
pthread_cond_init(&cond,NULL);
//創建三個線程:兩個消費者 一個生產者
pthread_t tid1,tid2,tid3;
pthread_create(&tid1, NULL, producer,"生產者");
pthread_create(&tid2, NULL, consumer,"消費者A");
pthread_create(&tid3, NULL, consumer,"消費者B");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
//銷燬互斥鎖
pthread_mutex_destroy(&mutex);
//銷燬條件變量
pthread_cond_destroy(&cond);
return 0;
}
運行結果:
6、信號量
信號量廣泛用於進程或線程間的同步和互斥,信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。
當信號量值大於 0 時,則可以訪問,否則將阻塞。
PV 原語是對信號量的操作,一次 P 操作使信號量減1(如果信號量爲0不允許再減,阻塞),一次 V 操作使信號量加1。
信號量數據類型爲:sem_t
信號量用於互斥:
如果是互斥,不管有多少個線程(進程),只需要一個信號量並初始化1,每個線程(進程)p—>具體任務—>v
信號量用於同步:
如果是同步:有幾個線程(進程),就要有幾個信號量,將需要先運行的線程(進程)的信號量初始化爲1,其他初始化0。每個線程(進程)都是先P自己的信號量,然後再V下一個線程(進程)的信號量。
6.1、初始化信號量:sem_init
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value)
功能:
創建一個信號量並初始化它的值。一個無名信號量在被使用前必須先初始化。
參數:
sem:信號量的地址
pshared:等於 0,信號量在線程間共享(常用);不等於0,信號量在進程間共享。
value:信號量的初始值
返回值:
成功:0
失敗: - 1
6.2、P操作 信號量減一 :sem_wait
int sem_wait(sem_t *sem);
功能: 將信號量減一,如果信號量的值爲0 則阻塞,大於0可以減一
參數:信號量的地址
返回值:成功返回0 失敗返回-1
嘗試對信號量減一
int sem_trywait(sem_t *sem);
功能: 嘗試將信號量減一,如果信號量的值爲0 不阻塞,立即返回 ,大於0可以減一
參數:信號量的地址
返回值:成功返回0 失敗返回-1
6.3、V 操作 信號量加一:
int sem_post(sem_t *sem);
功能:將信號量加一
參數:信號量的地址
返回值:成功返回0 失敗返回-1
6.4、銷燬信號量:sem_destroy
int sem_destroy(sem_t *sem);
功能: 銷燬信號量
參數: 信號量的地址
返回值:成功返回0 失敗返回-1
案例1:信號量的互斥(線程)
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include<semaphore.h>
//定義一個信號量
sem_t sem;
void myPrintf(char *str)
{
while(*str != 0)
{
printf("%c", *str);
fflush(stdout);
str++;
sleep(1);
}
}
void* my_fun01(void *arg)
{ //P -1
sem_wait(&sem);
myPrintf(arg);
//V +1
sem_post(&sem);
}
void* my_fun02(void *arg)
{
//P -1
sem_wait(&sem);
myPrintf(arg);
//V +1
sem_post(&sem);
}
int main()
{
//初始化信號量
//第二參數0表示線程同步互斥,1表示進程同步互斥
//第三個參數 是信號量的初始值
sem_init(&sem, 0, 1);
//創建兩個線程
pthread_t tid1,tid2;
pthread_create(&tid1,NULL, my_fun01, "hello");
pthread_create(&tid2,NULL, my_fun02, "world");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
//信號量撤銷
sem_destroy(&sem);
return 0;
}
運行結果:
案例2:信號量的同步(線程)
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include<semaphore.h>
//定義兩個信號量
sem_t sem1,sem2, sem3;
void myPrintf(char *str)
{
while(*str != 0)
{
printf("%c", *str);
fflush(stdout);
str++;
sleep(1);
}
}
void* my_fun01(void *arg)
{ //P -1
sem_wait(&sem1);
myPrintf(arg);
//V +1
sem_post(&sem2);
}
void* my_fun02(void *arg)
{
//P -1
sem_wait(&sem2);
myPrintf(arg);
//V +1
sem_post(&sem3);
}
void* my_fun03(void *arg)
{
//P -1
sem_wait(&sem3);
myPrintf(arg);
//V +1
sem_post(&sem1);
}
int main()
{
//初始化信號量
//第二參數0表示線程同步互斥,1表示進程同步互斥
//第三個參數 是信號量的初始值
sem_init(&sem1, 0, 1);//sem1先執行
sem_init(&sem2, 0, 0);
sem_init(&sem3, 0, 0);
//創建兩個線程
pthread_t tid1,tid2,tid3;
pthread_create(&tid1,NULL, my_fun01, "hello");
pthread_create(&tid2,NULL, my_fun02, "world");
pthread_create(&tid2,NULL, my_fun03, "來了 老弟");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
//信號量撤銷
sem_destroy(&sem1);
sem_destroy(&sem2);
sem_destroy(&sem3);
return 0;
}
運行結果:
7、無名信號量完成 有血緣關係的進程間 互斥和同步
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<semaphore.h>
#include<unistd.h>
#include <sys/mman.h>
void myPrintf(char *str)
{
while(*str != 0)
{
printf("%c", *str);
fflush(stdout);
str++;
sleep(1);
}
}
int main()
{
//互斥 只需要 定義一個信號量
sem_t *sem;
//匿名映射mmap MAP_ANONYMOUS -1不用打開文件
sem = mmap(NULL, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
//信號量 sem 初始化 第一個1表示進程間同步互斥 第二個1信號量的初始值1
sem_init(sem, 1, 1);
pid_t pid = fork();
if(pid == 0)//子進程
{
//p 操作-1
sem_wait(sem);
myPrintf("hello");
//v 操作+1
sem_post(sem);
}
else if(pid>0)//父進程
{
//p 操作-1
sem_wait(sem);
myPrintf("world");
//v 操作+1
sem_post(sem);
}
getchar();
//銷燬信號量
sem_destroy(sem);
//銷燬mmap
munmap(sem,sizeof(sem_t));
return 0;
}
運行結果:
無名信號量完成 有血緣關係的進程間 同步:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<semaphore.h>
#include<unistd.h>
#include <sys/mman.h>
void myPrintf(char *str)
{
while(*str != 0)
{
printf("%c", *str);
fflush(stdout);
str++;
sleep(1);
}
}
int main()
{
//同步 只需要 定義兩個信號量
sem_t *sem1,*sem2;
//匿名映射mmap MAP_ANONYMOUS -1不用打開文件
sem1 = mmap(NULL, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
sem2 = mmap(NULL, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
//信號量 sem 初始化 第一個1表示進程間同步互斥 第二個1信號量的初始值1
sem_init(sem1, 1, 1);
sem_init(sem2, 1, 0);
pid_t pid = fork();
if(pid == 0)//子進程
{
//p 操作-1
sem_wait(sem1);
myPrintf("hello");
//v 操作+1
sem_post(sem2);
}
else if(pid>0)//父進程
{
//p 操作-1
sem_wait(sem2);
myPrintf("world");
//v 操作+1
sem_post(sem1);
}
getchar();
//銷燬信號量
sem_destroy(sem1);
sem_destroy(sem2);
//銷燬mmap
munmap(sem1,sizeof(sem_t));
munmap(sem2,sizeof(sem_t));
return 0;
}
運行結果:
總結:
無名信號量 (sem_t sem) 線程同步互斥。
無名信號量(mmap 匿名映射) 有血緣關係的進程同步互斥
有名信號量 無血緣關係的進程同步互斥
8、有名信號量 沒有血緣進程互斥和同步
1、創建一個有名信號量
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
//信號量存在
sem_t *sem_open(const char *name, int oflag);
//信號量不存在
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
功能:
創建一個信號量
參數:
name:信號量的名字
oflag:sem_open函數的權限標誌
mode:文件權限(可讀、可寫、可執行 0777)的設置
value:信號量的初始值
返回值:
信號量的地址,失敗:SEM_FAILED
2、信號量的關閉:
int sem_close(sem_t *sem);
功能:關閉信號量
參數:信號量的的地址
返回值:成功0 失敗-1
3、信號量文件的刪除
#include <semaphore.h>
int sem_unlink(const char *name);
功能:刪除信號量的文件
參數:信號量的文件名
返回值:成功0 失敗-1
4、P操作 sem_wait V操作sem_post 銷燬信號量sem_destroy
有名信號量 沒有血緣進程互斥:
fork01.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<semaphore.h>
#include<unistd.h>
#include<fcntl.h>
void myPrintf(char *str)
{
while(*str != 0)
{
printf("%c", *str);
fflush(stdout);
str++;
sleep(1);
}
}
int main()
{
//互斥需要一個信號量
sem_t *sem = sem_open("sem", O_RDWR|O_CREAT,0666,1);
//p操作-1
sem_wait(sem);
myPrintf("hello hehe haha");
//v操作+1
sem_post(sem);
//關閉信號量
sem_close(sem);
return 0;
}
fork02.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<semaphore.h>
#include<unistd.h>
#include<fcntl.h>
void myPrintf(char *str)
{
while(*str != 0)
{
printf("%c", *str);
fflush(stdout);
str++;
sleep(1);
}
}
int main()
{
//互斥需要一個信號量
sem_t *sem = sem_open("sem", O_RDWR|O_CREAT,0666,1);
//p操作-1
sem_wait(sem);
myPrintf("world xixi lala");
//v操作+1
sem_post(sem);
//關閉信號量
sem_close(sem);
return 0;
}
運行結果:編譯的時候記得加-lpthread
有名信號量 沒有血緣進程同步:
fork01.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<semaphore.h>
#include<unistd.h>
#include<fcntl.h>
void myPrintf(char *str)
{
while(*str != 0)
{
printf("%c", *str);
fflush(stdout);
str++;
sleep(1);
}
}
int main()
{
//同步需要2個信號量
sem_t *sem1 = sem_open("sem1", O_RDWR|O_CREAT,0666,1);
sem_t *sem2 = sem_open("sem2", O_RDWR|O_CREAT,0666,0);
//p操作-1
sem_wait(sem1);
myPrintf("hello hehe haha");
//v操作+1
sem_post(sem2);
//關閉信號量
sem_close(sem1);
sem_close(sem2);
return 0;
}
fork02.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<semaphore.h>
#include<unistd.h>
#include<fcntl.h>
void myPrintf(char *str)
{
while(*str != 0)
{
printf("%c", *str);
fflush(stdout);
str++;
sleep(1);
}
}
int main()
{
//同步需要2個信號量
sem_t *sem1 = sem_open("sem1", O_RDWR|O_CREAT,0666,1);
sem_t *sem2 = sem_open("sem2", O_RDWR|O_CREAT,0666,0);
//p操作-1
sem_wait(sem2);
myPrintf("world xixi lala");
//v操作+1
sem_post(sem1);
//關閉信號量
sem_close(sem1);
sem_close(sem2);
return 0;
}
運行結果: