作者:李春港
出處:https://www.cnblogs.com/lcgbk/p/14673383.html
- 系統程式設計
- (一)程式
- (二)執行緒
- 1、建立執行緒與結束執行緒
- 2、執行緒的通訊
- (1)無名訊號量 sem_init()、sem_post()、sem_wait()、sem_destroy()
- (2)互斥鎖pthread_mutex_init()、pthread_mutex_lock()、pthread_mutex_unlock()、pthread_mutex_destroy()
- (3)讀寫鎖pthread_rwlock_init()、thread_rwlock_rdlock()、pthread_rwlock_wrlock()、pthread_rwlock_unlock()、pthread_rwlock_destroy()
- (4)條件變數pthread_cond_init()、pthread_cond_wait()、pthread_cond_broadcast()、pthread_cond_signal()、pthread_cond_destroy()
- (5)執行緒池
系統程式設計
這篇文章是對Linux的系統程式設計知識點做了一些簡單的總結。以下提到的知識點並非深入講解,只是大概講解了各個知識點的基本使用。如需要深入瞭解,可以針對某個知識點去深入學習。
(一)程式
linux系統程式設計技術點(程式,執行緒,執行緒池)
- 程式的概念,誕生,函式介面,意義。
- 程式之間通訊方式:有名管道,無名管道,訊號,訊息佇列,共享記憶體(效率最高),訊號量 --> 同一臺主機內部
套接字 --> 跨主機 - 程式的訊號集 --> 訊號集阻塞,解除阻塞
- 執行緒的概念,誕生,函式介面,意義。
- 執行緒通訊方式:訊號量,互斥鎖,讀寫鎖,條件變數
- 執行緒池 -> 初始化執行緒,新增執行緒,刪除執行緒 --> 批量處理資料
1、程式的概念
1. 程式與程式區別?
程式: 一堆待執行的程式碼 gcc hello.c(未編譯程式) -o hello(已編譯程式) --> 程式是一個靜態資料 --> 硬碟/nandfalsh
程式: 只有程式被載入到CPU中佔用CPU資源,根據每行程式碼效果,形成動態的過程 --> 動態過程
2. 程式如何誕生?
程式: project --> 執行: ./project --> 希望CPU載入project程式 --> 開啟一個程式
程式project:
int main()
{
int a; --> 棧區(執行記憶體)
return 0;
}
程式: ./project
CPU就會去硬碟中尋找到project,就會開始執行project靜態程式中每行程式碼,佔用空間就會在記憶體中申請
3. 當程式執行過程,除了會在記憶體中申請空間之外,系統還會為這個程式分配一個結構體
例子: 目錄:
讀取目錄中的每一項時就會結構體指標,指向該目錄項
結構體 --> 描述該目錄項(索引號,型別,檔名,檔名長度....)
struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* offset to the next dirent */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported by all file system types */
char d_name[256]; /* filename */
};
程式: ./project --> 開啟一個程式 --> 系統分配struct task_struct{}
這個結構體用於描述程式(程式ID號,訊號,檔案描述符資源...)
這個結構體在:/usr/src/linux-headers-3.5.0-23/include/linux/sched.h --> 參考task_struct.txt(在電腦F:\培訓2資料\01 系統程式設計\01)
結論1: struct dirent{} --> 描述目錄中的每一個目錄項的內容
struct task_struct{} --> 描述系統中每一個程式的內容
結論2: 當前linux系統中有幾個程式,就會有struct task_struct{}
4. 關於程式linux命令
1) 檢視整個linux系統程式的命令 --> pstree --> 父子關係
gec@ubuntu:~$ pstree
init─┬─NetworkManager───{NetworkManager}
├─accounts-daemon───{accounts-daemon}
├─acpid
├─atd
├─avahi-daemon───avahi-daemon
├─gnome-terminal─┬─bash───pstree
init程式: 只要linux系統開啟了,就會預設產生一個init程式,init程式又叫做祖先程式。
2)檢視程式的PID號 --> ps -ef
gec@ubuntu:~$ ps -ef --> 某個瞬間靜態資料
使用者名稱 程式號 父程式號 CPU佔用率 程式的啟動時間 終端裝置 程式持續的時間 程式名字
root 1 0 0 00:07 ? 00:00:00 /sbin/init
gec 4871 1 0 17:30 ? 00:00:05 gnome-terminal
gec 4877 4871 0 17:30 pts/0 00:00:00 bash
gec 5188 4877 0 18:29 pts/0 00:00:00 ps -ef
3)檢視CPU佔用率 --> top(按"q"返回終端上) --> 動態資料
4871 gec 20 0 93400 16m 11m S 2.3 ( CPU佔用率) 1.7 0:06.97 gnome-terminal
例如
int main()
{
while(1); --> 執行該程式時,沒有辦法結束 --> 一直佔用大量的CPU資源
}
5. 程式的狀態
就緒態 TASK_RUNNING 等待CPU資源
執行態 TASK_RUNNING 正在佔用CPU資源
暫停態 TASK_STOPPED 收到暫停訊號
睡眠態 TASK_INTERRUPTIBLE --> 可以響應 --> 淺度睡眠pause() --> 讓程式睡眠,直到收到一個訊號為止
TASK_UNINTERRUPTIBLE --> 不響應一般訊號 --> 深度睡眠 --> 除非致命訊號才會響應
殭屍態 EXIT_ZOMBIE 程式退出時,一定會變成殭屍態,佔用CPU資源(殭屍程式是當子程式比父程式先結束,
而父程式又沒有回收子程式,此時子程式將成為一個殭屍程式。
如果父程式先退出 ,子程式被init接管,子程式退出後init會回收其佔用的相關資源)
死亡態 EXIT_DEAD 程式退出時,如果有程式幫自己回收資源,那麼該程式就會從殭屍態變成死亡態
2、程式函式介面
./project --> 在linux系統直接開啟新的project程式
(1)fork()在程式內部建立新的子程式
fork() -- man 2 fork(它執行一次卻返回兩個值)
(兩個父子程式是在同時進行的,但誰先是隨機的)
//標頭檔案
<unistd.h> --> 與read,write,close...標頭檔案一致
//函式原型 pid_t: 程式ID號的資料型別
pid_t fork(void); --> 不需要傳遞任何的引數
//返回值:
成功:
父程式:子程式的PID號 (PID號沒有負數)
子程式:0
失敗:
父程式:-1
子程式:建立失敗
例子1:
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
printf("hello!\n");
fork(); --> 產生一個新的子程式,至於父子程式誰先誰後,那就是隨機的!
//從這句話開始,父子程式同時執行以下的程式碼:
printf("world!\n");
return 0;
}
結果1:
gec@ubuntu:/mnt/hgfs/fx9/01 系統程式設計/01/code$ ./fork
hello!
world! --> 父程式列印
gec@ubuntu:/mnt/hgfs/fx9/01 系統程式設計/01/code$ world! --> 子程式列印
為什麼會出現一個命令列提示符? --> 因為父程式結束了,就會出現!
結果2:
gec@ubuntu:/mnt/hgfs/fx9/01 系統程式設計/01/code$ ./fork
hello!
world! --> 子程式先列印
world! --> 父程式再列印
gec@ubuntu:/mnt/hgfs/fx9/01 系統程式設計/01/code$
例子2:使用fork() 返回值區別父子程式工作內容
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
printf("hello!\n");
pid_t x;
x = fork();
//父程式
if(x > 0)
{
usleep(200000); //確保子程式先執行!
printf("world!\n");
}
//子程式
if(x == 0)
{
printf("apple!\n");
}
return 0;
}
例子3:父子程式資源差異
- fork()後,子程式複製拷貝父程式大部分的資源(除了ID號)
- fork()分裂後,父子程式的資源是相互獨立 --> 在子程式中不能使用在父程式中定義的變數
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int a = 100;
pid_t x;
x = fork();
//父程式可以看到是一個.c檔案,子程式可以看作另外一個.c檔案
//父程式
if(x > 0)
{
a = 50;
printf("parent a = %d\n",a);//50
}
//子程式
if(x == 0)
{
printf("child a = %d\n",a);//100
}
return 0;
}
(2)getpid()、getppid()檢視自身PID號/檢視父程式的PID號
getpid() getppid() --- man 2 getpid
//標頭檔案
#include <sys/types.h>
#include <unistd.h>
//函式原型
pid_t getpid(void); //獲取自身程式PID號
pid_t getppid(void);//獲取父程式PID號
./project --> 開啟新的程式 --> 在程式中呼叫getpid()獲取pid號
//返回值:
總是成功!
例子1:分別在父子程式中列印自己與對方的ID號
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t x;
x = fork();
//父程式
if(x > 0)
{
printf("parent ID = %d\n",getpid());
printf("child ID = %d\n",x);
}
//子程式
if(x == 0)
{
printf("child ID = %d\n",getpid());
printf("parent ID = %d\n",getppid());
}
return 0;
}
結果分析:
父程式先執行:
gec@ubuntu:/mnt/hgfs/fx9/01 系統程式設計/01/code$ ./getpid
parent ID = 5583 --> 父程式列印
child ID = 5584 --> 父程式列印
gec@ubuntu:/mnt/hgfs/fx9/01 系統程式設計/01/code$ child ID = 5584 --> 子程式列印
parent ID = 1 --> 子程式列印
父程式先執行先退出,子程式就會變成孤兒程式,繼續尋找祖先程式(ID=1)幫子程式回收資源
子程式先執行
gec@ubuntu:/mnt/hgfs/fx9/01 系統程式設計/01/code$ ./getpid
child ID = 5591 --> 子程式列印
parent ID = 5590 --> 子程式列印
parent ID = 5590 --> 父程式列印
child ID = 5591 --> 父程式列印
不會出現孤兒程式!
(3)wait()、waitpid()子程式中資源回收
1. 程式狀態
執行態 --> 佔用CPU資源,正在執行程式碼
殭屍態 --> 佔用CPU資源,不執行程式碼
死亡態 --> 不佔用CPU資源
2. 解決殭屍問題?
1)當一個程式的父程式比自身先退出,系統指定init祖先程式為程式的繼父,等待子程式退出,回收資源。
2)當父程式還在執行時,主動回收資源。
3. 如何回收資源?
1)wait()
--- man 2 wait ---> 父程式主動回收資源情況
(第一種情況wait()就是用來阻塞父程式的繼續往下執行,讓另外的子程式執行完退出回收後再退出阻塞繼續執行(無論子程式中有沒有sleep()或者其他情況都一樣),但父程式依然還在;第二種情況是子程式比父程式已經先結束,則wait()直接回收不需要阻塞等待回收,如果不用wait(),則最後由祖先init來回收)
//標頭檔案
#include <sys/types.h>
#include <sys/wait.h>
//函式原型
pid_t wait(int *status);
status: 監聽子程式的退出狀態指標變數
//返回值:
成功: 退出的子程式的ID號
失敗: -1
2)waitpid()
針對wait()函式封裝 -- waitpid
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
pid: 指定等待的程式號
<-1 : 等待這個負數的絕對值的ID號的子程式
-1: 等待任意一條子程式
0: 等待程式組內部的任意的一個
>0: 指定等待這個數值ID號的程式
status: 監聽子程式的退出狀態指標變數
options:
WNOHANG : 非阻塞等待子程式退出狀態
WUNTRACED :監聽子程式的暫停訊號
WCONTINUED:監聽子程式的繼續訊號
0: 阻塞等待
wait(&status) 等價於 waitpid(-1, &status, 0);
例子1:主動回收資源
情況1: fork()後,子程式先退出;父程式後退出,再回收子程式的資源。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
int main()
{
pid_t x;
x = fork();
if(x > 0)
{
int i;
int state;
for(i=0;i<20;i++)
{
sleep(1);
printf("i=%d\n",i);
}
//wait(NULL);//不關心子程式的狀態
wait(&state);//阻塞
printf("state = %d\n",state);
}
if(x == 0)
{
sleep(1);
printf("helloworld!\n"); //馬上結束!
}
}
結果:在倒數時間內,子程式是一個殭屍程式
例子2:fork()後,父程式先執行完,但是主動回收資源。子程式後退出。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
int main()
{
pid_t x;
x = fork();
if(x > 0)
{
int state;
wait(&state);//阻塞
printf("state = %d\n",state);
}
if(x == 0)
{
int i;
for(i=0;i<20;i++)
{
sleep(1);
printf("i=%d\n",i);
}
printf("helloworld!\n"); //馬上結束!
}
}
結果: 父程式一直阻塞等待子程式,子程式退出時,會變成殭屍程式,但是馬上就會被父程式回收!
(4)return 0、exit(0)程式的退出問題
(exit(0)表示正常退出整個程式,在子函式中return表示返回一個值,如果在主函式中用return
系統會自動呼叫exit(0)退出整個程式)
int fun()
{
printf("helloworld2!\n");
//return 0; //當前函式fun結束,返回到被呼叫的地方
exit(0); //直接退出整個程式,如果程式沒有fork()產生子程式也就是整個程式只有一個程式,也就是結束整個程式
}
int main()
{
printf("helloworld1!\n");
fun();
printf("helloworld3!\n");
return 0;
}
- 重新整理緩衝區,再退出 --> exit() --- man 3 exit
#include <stdlib.h>
void exit(int status);
status: 退出狀態值 0: 正常退出程式 非0: 異常退出
- 不清理緩衝區,直接退出 --> _exit() -- man 2 _exit
#include <unistd.h>
void _exit(int status);
例子1:用程式相關API函式程式設計一個程式,使之產生一個程式扇:父程式產生一系列子程式,每個子程式列印自己的PID然後退出。要求父程式最後列印PID。
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
int n,i;
pid_t x;
//1. 要求使用者輸入子程式的個數
scanf("%d",&n);//5
//2. 產生一個程式扇
for(i=0;i<n;i++)
{
x = fork();
if(x>0)
{
continue;
}
if(x == 0)
{
break;
}
}
//有1個父程式,5個子程式
//3. 根據父子程式處理不同的事情
if(x > 0)
{
int state;
for(i=0;i<n;i++)
{
wait(&state);
}//迴圈結束了,代表所有的子程式都已經退出了!
printf("parent PID = %d\n",getpid());
exit(0);
}
if(x == 0)
{
printf("chile PID = %d\n",getpid());
exit(0);
}
return 0;
}
例子2:用程式相關API函式編寫一個程式,使之產生一個程式鏈:程式派生一個子程式後,然後列印出自己的PID,然後退出,該子程式繼續派生子程式,然後列印PID,然後退出,以此類推。
要求:1、實現一個父程式要比子程式先列印PID的版本。(即列印的PID一般是遞增的)
2、實現一個子程式要比父程式先列印PID的版本。(即列印的PID一般是遞減的)
要求1:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
//1. 產生一個程式鏈
int n,i;
pid_t x;
scanf("%d",&n);//3
for(i=0;i<n;i++)
{
x = fork();
if(x > 0)
{
break;
}
if(x == 0)
{
continue;
}
}
//有3個父程式,1個子程式
//3個父程式執行:
if(x > 0)
{
printf("parent PID = %d\n",getpid());
exit(0);
}
//最後的那個子程式執行:
if(x == 0)
{
exit(0);
}
}
要求2:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
//1. 產生一個程式鏈
int n,i;
pid_t x;
scanf("%d",&n);//5
for(i=0;i<n;i++)
{
x = fork();
if(x > 0)
{
break;
}
if(x == 0)
{
continue;
}
}
if(x > 0)
{
wait(NULL);
printf("PID = %d\n",getpid());
exit(0);
}
//最後的那個子程式,它的退出,可以觸發倒數第二個程式的wait()
if(x == 0)
{
exit(0);
}
}
(5)exec函式族(替換子程式)
(在電腦F:\培訓2資料\01 系統程式設計\01)
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
path:需要執行的那個程式的絕對路徑
file:檔名
arg: 執行程式時,需要的命令列引數列表,以"NULL"作為結束標誌
argv:把全部的引數放置在陣列中
envp: 環境變數
例子1: 就以"ls -l"為例子,替換子程式
遇到exec族函式,後面都會被替換掉!
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t x;
x = fork();
if(x > 0)
{
sleep(1);
printf("helloworld!\n");
}
if(x == 0)
{
printf("bbbbbbbb\n");
//execl("/bin/ls","ls","-l",NULL) ;
//execlp("ls","ls","-l",NULL);
//execle("/bin/ls","ls","-l",NULL,NULL);
char *arg[3] = {"ls","-l",NULL};
//execv("/bin/ls",arg);
//execvp("ls",arg);
execvpe("ls",arg,NULL);
printf("aaaaa\n");
}
}
3、程式的通訊
程式間的通訊方式,總結起來主要有如下這些:
- 無名管道(PIPE)和有名管道(FIFO)
- 訊號(signal)
- system V-IPC之共享記憶體
- system V-IPC之訊息佇列
- system V-IPC之訊號量(較為複雜、麻煩,在程式中逐步被POSIX有名訊號量取代)
(注意:linux中的訊號量有三種:system V-IPC之訊號量、POSIX有名訊號量、POSIX無名訊號量;system V-IPC之訊號量、POSIX有名訊號量主要用於程式;POSIX無名訊號量主要用於執行緒;複雜度從大到小:system V-IPC之訊號量、POSIX有名訊號量、POSIX無名訊號量;) - 套接字
IPC物件: 訊息佇列,共享記憶體,訊號量
檢視Linux中IPC物件的命令
1)檢視所有的IPC物件 ipcs -a
2)檢視訊息佇列: ipcs -q
3)檢視共享記憶體: ipcs -m
4)檢視訊號量: ipcs -s
唯一識別符號key值 ID號
key shmid
key semid
key msqid
5)如何刪除IPC物件?
訊息佇列: ipcrm -q key值 / ipcrm -q msqid
共享記憶體: ipcrm -m key值 / ipcrm -m shmid
訊號量: ipcrm -s key值 / ipcrm -s semid
(0)獲取唯一的識別符號key值ftok() 、
//標頭檔案
#include <sys/types.h>
#include <sys/ipc.h>
//函式原型
key_t ftok(const char *pathname, int proj_id);
pathname: 一個存在而且合法的路徑 "."
proj_id: 非0整數 10/20
//返回值:
成功: key值
失敗: -1
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
int main()
{
key_t key1,key2;
key1 = ftok(".",10);
key2 = ftok(".",20);
printf("%d\n",key1);
printf("%d\n",key2);
}
(1)管道
無名管道pipe()
無名管道 pipe() --> 建立無名管道 --> man 2 pipe
#include <unistd.h>
int pipe(int pipefd[2]); --> 產生兩個檔案描述符
//檔案描述符
pipefd[0] refers to the read end of the pipe. --> 讀端
pipefd[1] refers to the write end of the pipe. --> 寫端
//資料必須要從管道中讀取走了,才能進行寫入!
Data written to the write end of the pipe is buffered by the
kernel until it is read from the read end of the pipe.
pipefd: 有兩個int型別的資料的陣列,用於存放讀端與寫端的檔案描述符
返回值:
成功:0
失敗:-1
注意:
- 無名管道只能作用於親緣關係程式(父子程式,兄弟程式,祖孫程式...)
- 無名管道半雙工,讀端與寫端分開
- 無名管道沒有名字,所以不是一個檔案,不能用lseek函式定位檔案。
例子1:通過無名管道,讓子程式傳送資料給父程式,然後在父程式中列印出來。
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
int main(int argc,char *argv[])
{
int fd[2] = {0};//定義陣列,有兩個int型別
pid_t x;
int ret;
//1. 建立無名管道
ret = pipe(fd);
if(ret < 0)
{
printf("pipe error!\n");
}
//2. 產生子程式
x = fork();
if(x > 0)
{
char buf[10] = {0};
//阻塞讀取,等到子程式執行完了才不阻塞!
while(1)
{
read(fd[0],buf,sizeof(buf));
printf("from child = %s\n",buf);
if(strncmp(buf,"show",4) == 0)
{
printf("show jpeg!\n");
}
}
}
if(x == 0)
{
char buf[10] = "hello";
while(1)
{
write(fd[1],buf,strlen(buf));
}
}
}
fifo有名管道mkfifo()
有名管道建立一個新的檔案
程式1寫入資料 --> 有名管道 --> 程式2讀取資料
linux下一切都是檔案 --> 有名管道都是檔案 ---> 管道檔案的路徑不要出現在共享目錄
建立有名管道
//標頭檔案
#include <sys/types.h>
#include <sys/stat.h>
//函式原型
int mkfifo(const char *pathname, mode_t mode);
pathname: 管道檔案的路徑
mode:管道起始八進位制許可權 0777
//返回值
成功:0
失敗:-1
注意:
1)有名管道作用於任意的兩個程式之間
2)由於有名管道是有檔名,所以可以使用檔案IO介面處理管道檔案 open/read/write/close/
3)寫入資料具有原子性,要不寫入資料成功,要不寫入失敗
例子1: 管道檔案: /home/gec/fifo
1.c --> 負責從有名管道中讀取資料出來
2.c --> 負責從有名管道中寫入資料
1、c讀端
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
//1. 建立有名管道
int fd;
fd = open("/home/gec/fifo",O_RDWR);
if(fd < 0)//檔案不存在!
{
int ret = mkfifo("/home/gec/fifo",0777);
if(ret < 0)
printf("mkfifo error!\n");
//2. 訪問有名管道檔案
fd = open("/home/gec/fifo",O_RDWR); //一定要給可讀可寫許可權,否則管道就會阻塞!
if(fd < 0)
printf("open error!\n");
}
//3. 讀取管道的資料
char buf[50] = {0};
while(1)
{
bzero(buf,50);
read(fd,buf,sizeof(buf));
printf("from fifo = %s",buf);
}
//4. 關閉檔案資源
close(fd);
return 0;
}
2、c寫端
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
//1. 建立有名管道
int ret = mkfifo("/home/gec/fifo",0777);
if(ret < 0)
printf("mkfifo error!\n");
//2. 訪問有名管道檔案
int fd = open("/home/gec/fifo",O_RDWR); //一定要給可讀可寫許可權,否則管道就會阻塞!
if(fd < 0)
printf("open error!\n");
//3. 讀取管道的資料
char buf[50] = {0};
while(1)
{
bzero(buf,50);
fgets(buf,50,stdin); //包含\n在內
write(fd,buf,strlen(buf));
}
//4. 關閉檔案資源
close(fd);
return 0;
}
(2)訊號 raise()、kill()、signal()、pause()
在Linux中有非常多訊號,通過"kill -l"列出所有的訊號。(kill在Linux中作為命令時,傳送訊號的意義)
gec@ubuntu:/mnt/hgfs/fx9/01 系統程式設計/02/code/FIFO$ kill -l
(1~31)號訊號屬於非實時訊號 在Linux中響應以下訊號沒有固定的次序的
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
2) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS
The signals SIGKILL and SIGSTOP cannot be caught or ignored
9) SIGKILL //殺死程式
19) SIGSTOP //暫停程式 --> 不能被捕捉,阻塞,必須響應!
(34~64)訊號實時訊號(從大到小依次響應)
34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
35) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
36) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
37) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
38) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
39) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
40) SIGRTMAX-1 64) SIGRTMAX
訊號的名字其實是一個巨集定義來的,被定義在標頭檔案: /usr/include/asm-generic/signal.h
SIG訊號名字 訊號值
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
1、 訊號由誰來產生/傳送出來?
1)系統某些特定函式/情況而產生
按下"ctrl + C" --> 等於產生一個SIGINT
alarm() --> 等於產生一個SIGALRM
2)由使用者來產生一個訊號,發給程式
使用命令1: kill - send a signal to a process --> 給某個程式號ID傳送訊號
kill -訊號值/-訊號名字 ID
使用命令2: killall - kill processes by name --> 給某個程式名字傳送訊號
killall -訊號值/-訊號名字 程式名字
例子1:
執行命令 程式名字 程式號ID
./project project 1000
kill -9 1000 / kill -KILL 1000
killall -9 project / killall -KILL project
--> 一般結合system()一起使用!
2、訊號的捕捉與傳送
捕捉: 提前告知程式將來收到某個訊號時,會處理什麼事情。
傳送: 給某個程式傳送訊號
1)訊號的傳送 --- kill() --> man 2 kill
//標頭檔案
#include <sys/types.h>
#include <signal.h>
//函式原型
int kill(pid_t pid, int sig);
pid:對方程式的PID號
sig:需要給對方程式傳送的訊號
//返回值:
成功: 至少發出一個訊號 0
失敗: 一個訊號沒有傳送出去 -1
2)自己給自己發訊號 raise() -- man 3 raise
//標頭檔案
#include <signal.h>
//函式原型
int raise(int sig);
sig: 訊號值
//返回值:
成功: 0
失敗: 非0
raise(sig); 等價於 kill(getpid(), sig);
例子1:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void fun(int sig)
{
printf("catch sig = %d\n",sig);
}
int main()
{
signal(SIGUSR1,fun);
sleep(1);
//raise(SIGUSR1);
kill(getpid(),SIGUSR1);
return 0;
}
3) 訊號的捕捉 --- signal() --> man 2 signal
//標頭檔案
#include <signal.h>
typedef void (*sighandler_t)(int)
/*該語法的解析:float (*f)(); 這時,f就不是和()結合而成為一個函式名,而是和*結合成為一個指標名,這個指標,是指向函式入口的函式指標,而這個函式,返回值是浮點型。在這裡,typedef void (*sighandler_t)(int) 也可以寫成void (*sighandler_t)(int),typedef 在語句中所起的作用不過就是把語句原先定義變數的功能變成了定義型別的功能而已。*/
//函式原型
sighandler_t signal(int signum, sighandler_t handler);
signum:需要捕捉的訊號
handler:
1. SIG_IGN 忽略該訊號(等於沒有收到訊號)
2. signal handler 處理函式 void fun(int) 收到訊號了
3. SIG_DFL 預設動作 收到訊號了
//返回值:
成功: 返回第二個引數的地址
失敗: SIG_ERR
例子1:子程式給父程式傳送10訊號,父程式接收到訊號後列印出來訊號值。
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
void fun(int sig)
{
printf("catch sig = %d\n",sig);
}
int main(int argc,char *argv[])
{
pid_t x;
x = fork();
//父程式
if(x > 0)
{
signal(SIGUSR1,fun);
while(1);
}
//子程式發
if(x == 0)
{
sleep(3);
kill(getppid(),SIGUSR1);
}
return 0;
}
例子2:忽略訊號
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
pid_t x;
x = fork();
//父程式
if(x > 0)
{
signal(SIGINT,SIG_IGN); //收到SIGINT,不會理會!
pause();//一直在等待程式不會結束
}
//子程式發
if(x == 0)
{
sleep(3);
kill(getppid(),SIGINT);
}
return 0;
}
例子3:預設動作
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
pid_t x;
x = fork();
//父程式
if(x > 0)
{
signal(SIGINT,SIG_DFL); //不阻塞等待訊號
pause();//阻塞等待,等signal收到SIGINT訊號時,則結束程式
}
//子程式發
if(x == 0)
{
sleep(3);
kill(getppid(),SIGINT);
}
return 0;
}
4)掛起程式(也就是在哪裡等待),直到收到一個訊號且執行處理函式為止 pause() --- man 2 pause --> 一般與訊號的捕捉一起使用
//標頭檔案
#include <unistd.h>
//函式原型
int pause(void);
//返回值:
收到能夠捕捉的訊號時,才能返回捕捉到訊號值
收到不能捕捉的訊號,返回 -1
The signals SIGKILL and SIGSTOP cannot be caught or ignored
9) SIGKILL //殺死程式
19) SIGSTOP //暫停程式 --> 不能被捕捉,阻塞,必須響應!
注意:例子在上面訊號捕捉的例子2、3
練習題
練習1:
用命名管道分別寫一個伺服器程式和一個客戶機程式,客戶機的父程式負責每隔一秒產生一個子程式(形成一個程式扇),而每個子程式則往FIFO寫入自己的PID號碼。伺服器負責從該FIFO中讀取資料並將之列印到螢幕上
client.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
pid_t x;
//1. 在客戶機中建立管道
if(access("/home/gec/FIFO",F_OK))
{
mkfifo("/home/gec/FIFO",0777);
}
//2. 訪問管道檔案
int fd = open("/home/gec/FIFO",O_RDWR);
//3. 產生一個程式扇
while(1)
{
x = fork();
if(x > 0)
{
sleep(1);
continue;
}
if(x == 0)
{
char buf[50];
bzero(buf,50);
sprintf(buf,"child PID = %d",getpid());
write(fd,buf,strlen(buf));
break;
}
}
return 0;
}
server.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
//1. 在伺服器中建立管道
if(access("/home/gec/FIFO",F_OK))
{
mkfifo("/home/gec/FIFO",0777);
}
//2. 訪問管道檔案
int fd = open("/home/gec/FIFO",O_RDWR);
//3. 不斷讀取管道中內容
char buf[50];
while(1)
{
bzero(buf,50);
read(fd,buf,sizeof(buf));
printf("buf:%s\n",buf);
}
return 0;
}
練習2:
編寫一段程式,使用系統呼叫函式fork( )建立兩個子程式,再用系統呼叫函式signal( )讓父程式捕捉訊號SIGINT(用kill命令來觸發),當捕捉到中斷訊號後,父程式用系統呼叫函式kill( )向兩個子程式發出訊號,子程式捕捉到父程式發來的訊號後,分別輸出下列資訊後終止:
Child process 1 is killed by parent!
Child process 2 is killed by parent!
或者
Child process 2 is killed by parent!
Child process 1 is killed by parent!
父程式等待兩個子程式終止後,輸出以下資訊後終止:
Parent process exit!
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
pid_t pid1,pid2;
void parent_fun(int sig)
{
//傳送訊號給孩子
kill(pid1,SIGUSR1);
kill(pid2,SIGUSR2);
}
void fun1(int sig)
{
printf("Child process 1 is killed by parent!\n");
}
void fun2(int sig)
{
printf("Child process 2 is killed by parent!\n");
}
int main()
{
//生第一個小孩
pid1 = fork();
if(pid1 > 0)
{
//生第二個小孩
pid2 = fork();
if(pid2 > 0)
{
signal(SIGINT,parent_fun);
wait(NULL);
wait(NULL);
printf("Parent process exit!\n");
exit(0);
}
//第二個子程式
if(pid2 == 0)
{
signal(SIGUSR2,fun2);
pause();
exit(0);
}
}
//第一個子程式
if(pid1 == 0)
{
signal(SIGUSR1,fun1);
pause();
exit(0);
}
}
練習3:
父程式開始播放音樂,子程式每隔10秒顯示一張圖片,共3張,顯示完之後音樂停止。
程式的框架:
void fun(int sig)
{
system("killall -9 madplay");
exit(0);
}
int main()
{
pid_t x;
if(x > 0)
{
signal(SIGUSR1,fun);
system("madplay xxx.mp3");
}
if(x == 0)
{
printf("show!");
sleep(10);
printf("show!");
sleep(10);
kill(getppid(),SIGUSR1);
}
}
(3)system V-IPC之訊息佇列msgget()、msgsnd()、msgrcv()、msgctl()
管道: 不能讀取特定的資料,只要管道中有資料,就會全部讀取
訊息佇列: 可以讀取特定的資料
1)根據key值為訊息佇列申請ID號 --- msgget() --> man 2 msgget
//標頭檔案
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//函式原型
int msgget(key_t key, int msgflg);
key: 訊息佇列對應的key值
msgflg: IPC_CREAT|0666: 不存在則建立
IPC_EXCL: 存在則報錯
The execute permissions are not used --> 不能使用0777
//返回值:
成功: 訊息佇列的ID號
失敗: -1
2)傳送訊息給訊息佇列 --- msgsnd() --> man 2 msgsnd
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid: 訊息佇列的ID號
msgp: 傳送到訊息佇列的資料緩衝區,型別必須是:
struct msgbuf {
long mtype; 資料型別 >0
char mtext[1]; 資料正文
};
msgsz: 正文mtext的位元組數
msgflg: 普通屬性填0
//返回值:
成功: 0
失敗: -1
3)接收訊息佇列的資料 --- msgrcv() --- man 2 msgrcv
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msqid: 訊息佇列的ID號
msgp: 接收資料的緩衝區,型別必須是:
struct msgbuf {
long mtype; 資料型別 >0
char mtext[1]; 資料正文
};
msgsz: 正文mtext的位元組數
msgtyp:接收訊息的型別
msgflg: 普通屬性填0
//返回值
成功: 接收的正文的位元組數
失敗: -1
4) 設定訊息佇列的屬性(刪除訊息佇列) --- msgctl() ---> man 2 msgctl
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid: 訊息佇列的ID號
cmd:操作的命令字 IPC_RMID --> 刪除訊息佇列
buf: 刪除 --> NULL
//返回值
成功: 0
失敗: -1
例子1:實現兩個程式之間通訊!
寫端.c:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct msgbuf{
long mtype;
char mtext[50];
};
int main()
{
key_t key;
int msgid,ret;
//1. 獲取key值
key = ftok(".",10);
printf("key = %d\n",key);
//2. 根據key值申請ID號
msgid = msgget(key,IPC_CREAT|0666);
printf("msgid = %d\n",msgid);
//3. 傳送訊息到訊息佇列
struct msgbuf gec;
gec.mtype = 10;
bzero(gec.mtext,50);
strcpy(gec.mtext,"hello");
ret = msgsnd(msgid,&gec,strlen(gec.mtext),0);
if(ret == -1)
{
printf("msgsnd error!\n");
exit(1);
}
return 0;
}
讀端.c:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct msgbuf{
long mtype;
char mtext[50];
};
int main()
{
key_t key;
int msgid,ret;
//1. 獲取key值
key = ftok(".",10);
printf("key = %d\n",key);
//2. 根據key值申請ID號
msgid = msgget(key,IPC_CREAT|0666);
printf("msgid = %d\n",msgid);
//3. 從訊息佇列中讀取資料
struct msgbuf gec;
bzero(gec.mtext,50);
ret = msgrcv(msgid,&gec,sizeof(gec.mtext),10,0);
if(ret == -1)
{
printf("msgrcv error!\n");
}
printf("gec.mtext = %s\n",gec.mtext);
//4. 刪除訊息佇列
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
(4)system V-IPC之共享記憶體shmget()、shmat()、shmdt()、shmctl()
1. 獲取共享記憶體ID號 --- shmget() --- man 2 shmget
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
key: 唯一識別符號key值
size: 1024整數倍 2048 4096 8192
msgflg: IPC_CREAT|0666: 這個IPC物件不存在則建立
IPC_EXCL: 存在則報錯
//返回值:
成功: 共享記憶體的ID號
失敗: -1
2. 根據共享記憶體的ID號對映記憶體空間地址 shmat() --- man 2 shmat
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid: 共享記憶體的ID號
shmaddr: NULL ---> 系統分配空間(99.99%)
不為NULL ---> 使用者自定義空間的位置
shmflg: 普通屬性0
//返回值:
成功: 共享記憶體的首地址
失敗: -1
3. 撤銷對映 --- shmdt() --- man 2 shmdt
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
shmaddr: 需要解除對映的記憶體的首地址
//返回值:
成功:0
失敗:-1
4. 設定共享記憶體的屬性(刪除ID號) --- shmctl() --- man 2 shmctl
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid: 共享記憶體的ID號
cmd: 操作命令 IPC_RMID
buf: 刪除 --> NULL
//返回值:
成功: 0
失敗: -1
例子1:
寫端.c
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
int main()
{
key_t key;
int shmid;
char *p = NULL;
//1. 申請key值
key = ftok(".",10);
//2. 根據key值申請共享記憶體的ID號
shmid = shmget(key,8192,IPC_CREAT|0666);
//3. 根據ID號去記憶體中對映一片記憶體空間
p = (char *)shmat(shmid,NULL,0);
//4. 清空記憶體空間
bzero(p,8192);
//5. 不斷從鍵盤中獲取字串,寫入到記憶體中
while(1)
{
fgets(p,8192,stdin);
if(strncmp(p,"quit",4) == 0)
break;
}
return 0;
}
讀端.c
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
int main()
{
key_t key;
int shmid;
char *p = NULL;
//1. 申請key值
key = ftok(".",10);
//2. 根據key值申請共享記憶體的ID號
shmid = shmget(key,8192,IPC_CREAT|0666);
//3. 根據ID號去記憶體中對映一片記憶體空間
p = (char *)shmat(shmid,NULL,0);
//4. 讀取共享記憶體的資料
while(1)
{
printf("from shm: %s",p);
usleep(500000);
if(strncmp(p,"quit",4) == 0)
break;
}
//5. 撤銷對映
shmdt(p);
//6. 刪除共享記憶體
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
(5)有名訊號量sem_open()、sem_post()、sem_wait()、sem_close()、sem_unlink()
1.建立或者開啟一個有名訊號量 --- sem_open() --- man 3 sem_open
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
name: 有名訊號量名字,以"/"開頭 例子: "/sem"
oflag:
O_CREAT 如果不存在,則建立!
O_EXCL 如果存在,則報錯!
mode: 八進位制許可權 0777
value: 訊號量初始化的值 0 / 1
//返回值:
成功: 訊號量變數的地址
失敗: 錯誤碼
2. 資源數修改(P/V)
中國讀者常常不明白這一同步機制為什麼叫PV操作,原來這是狄克斯特拉用荷蘭文定義的,因為在荷蘭文中,通過叫passeren,釋放叫vrijgeven,PV操作因此得名。P(S):①將訊號量S的值減1;V(S):①將訊號量S的值加1。
資源數加1 --> sem_post() --->V操作
資源數減1 --> sem_wait() --->P操作
#include <semaphore.h>
int sem_post(sem_t *sem);
int sem_wait(sem_t *sem); //如果訊號量值大於0,該函式立即返回;如果當前訊號量為0,則阻塞等待,知道訊號量大於0
sem: 訊號量的地址
//返回值:
成功:0
失敗:-1
3. 關閉訊號量 --- sem_close() --- man 3 sem_close
#include <semaphore.h>
int sem_close(sem_t *sem);
sem: 訊號量的地址
//返回值:
成功:0
失敗: -1
4. 刪除訊號量 --- sem_unlink --- man 3 sem_unlink
#include <semaphore.h>
int sem_unlink(const char *name);
name: 有名訊號量的名字
//返回值:
成功:0
失敗:-1
例子:
snd.c
#include <semaphore.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/sem.h>
#include <fcntl.h>
#include <sys/stat.h>
int main()
{
key_t key;
int shmid;
sem_t *sem = NULL;
//1. 共享記憶體處理
key = ftok(".",10);
shmid = shmget(key,8192,IPC_CREAT|0666);
char *p = (char *)shmat(shmid,NULL,0);
//2. 建立/開啟有名訊號量
sem = sem_open("/sem",O_CREAT,0777,0);
//3. 不斷寫入資料到共享記憶體
while(1)
{
fgets(p,8192,stdin);
sem_post(sem); //資源數加1
if(strncmp(p,"quit",4) == 0)
break;
}
sem_close(sem);
}
rcv.c
#include <semaphore.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/sem.h>
#include <fcntl.h>
#include <sys/stat.h>
int main()
{
key_t key;
int shmid;
sem_t *sem = NULL;
//1. 共享記憶體處理
key = ftok(".",10);
shmid = shmget(key,8192,IPC_CREAT|0666);
char *p = (char *)shmat(shmid,NULL,0);
//2. 建立/開啟有名訊號量
sem = sem_open("/sem",O_CREAT,0777,0);
//3. 不斷等待資源數為1
while(1)
{
//直到資源數大於0,這個函式就會返回!
sem_wait(sem);
printf("p = %s",p);
if(strncmp(p,"quit",4) == 0)
break;
}
sem_close(sem);
sem_unlink("/sem");
//撤銷對映
shmdt(p);
//刪除共享記憶體
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
4、訊號集
一. 訊號集概念
一堆訊號的集合,如果想同時設定多個訊號的屬性,我們會把全部的訊號加入到訊號集中,再設定阻塞屬性。
二. 什麼是阻塞屬性?
響應: 訊號到達時,馬上響應該訊號的動作
忽略: 訊號到達時,不響應該訊號,就等於是沒有收到該訊號
阻塞: 如果一個訊號集中的一個訊號設定了阻塞狀態,那麼該訊號達到,會暫停不會響應,直到阻塞狀態解除為止,才去響應訊號
丟棄: 如果一個訊號到達時,如果不能馬上響應,則以後訊號都不會響應。
三. 關於訊號集的處理函式 -- man 3 sigemptyset
1.關於訊號如何加入,刪除,清空訊號集合函式介面
<signal.h>
//清空訊號集set
int sigemptyset(sigset_t *set);
set:訊號集變數的地址
//將linux所有的訊號加入到訊號集set
int sigfillset(sigset_t *set);
//將指定的訊號signum加入訊號集合set中
int sigaddset(sigset_t *set, int signum);
signum: 指定加入訊號集的訊號值
//把指定的訊號signum從訊號集中刪除
int sigdelset(sigset_t *set, int signum);
以上函式的返回值:
返回值:
成功: 0
失敗: -1
//判斷訊號signum是否在訊號集中
int sigismember(const sigset_t *set, int signum);
返回值:
在集合中: 1
不在集合中: 0
失敗: -1
2. 設定訊號集阻塞掩碼
#include <signal.h>
int sigprocmask(int how, const sigset_t * set, sigset_t * oset);
how:
SIG_BLOCK : 設定為阻塞狀態
SIG_UNBLOCK: 設定為解除阻塞狀態
set: 設定阻塞狀態的訊號集
oset: 一般不關注之前訊號集合狀態,設定NULL
結論: 在阻塞狀態下,如果收到多個相同的訊號,則在解除阻塞時,只會響應一次!
例子1:
例子:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
void fun(int sig)
{
printf("catch sig = %d\n",sig);
}
int main()
{
pid_t x;
x = fork();
//父程式接收訊號,但是訊號設定阻塞狀態
if(x > 0)
{
int ret,i;
//1. 捕捉該訊號
signal(SIGINT,fun);
//2. 定義訊號集,想辦法把SIGINT加入到訊號集中
sigset_t set;
//3. 清空訊號集
sigemptyset(&set);
//4. 把SIGINT加入到訊號集中
sigaddset(&set,SIGINT);
//5. 判斷SIGINT是否在訊號集中
ret = sigismember(&set,SIGINT);
if(ret == 1)
printf("SIGINT is member!\n");
else
{
printf("SIGINT not is member!\n");
exit(1);
}
//6. 設定訊號集為阻塞狀態
sigprocmask(SIG_BLOCK,&set,NULL);
for(i=10;i>0;i--)
{
sleep(1);
printf("%d\n",i);
}
//7. 解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL);
wait(NULL);
exit(0);
}
//子程式傳送訊號
if(x == 0)
{
sleep(1);
kill(getppid(),SIGINT);
printf("I send signal to parent!\n");
exit(0);
}
}
例子2:如果在父程式中設定阻塞狀態,那麼他產生的子程式會不會也對這個訊號阻塞的?
結果:會。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
void fun(int sig)
{
printf("catch sig = %d\n",sig);
}
int main()
{
int ret;
pid_t x;
//1. 捕捉該訊號
signal(SIGINT,fun);
//2. 定義訊號集,想辦法把SIGINT加入到訊號集中
sigset_t set;
//3. 清空訊號集
sigemptyset(&set);
//4. 把SIGINT加入到訊號集中
sigaddset(&set,SIGINT);
//5. 判斷SIGINT是否在訊號集中
ret = sigismember(&set,SIGINT);
if(ret == 1)
printf("SIGINT is member!\n");
else
{
printf("SIGINT not is member!\n");
exit(1);
}
//6. 設定訊號集為阻塞狀態
sigprocmask(SIG_BLOCK,&set,NULL);
//7. 帶著阻塞的狀態產生一個子程式
x = fork();
if(x > 0)
{
sleep(5);
sigprocmask(SIG_UNBLOCK,&set,NULL);
while(1);
}
if(x == 0)
{
int i;
for(i=20;i>0;i--)
{
sleep(1);
printf("%d\n",i);
}
//7. 解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL);
}
}
(二)執行緒
程式與執行緒之間的關係:
程式 ./xxxx ---> 開啟新的程式 --> 系統最小的分配單位
執行緒 在程式內部申請一個資源,是程式中最小的分配單位 / 系統中排程最小單位
執行緒的函式介面
封裝線上程庫 /usr/lib/i386-linux-gnu/libpthread.so
執行緒庫標頭檔案: /usr/include/pthread.h
只要工程中涉及到了執行緒的函式,在編譯時都要連結 -lpthread
如:gcc 1.c -o 1 -lpthread
1、建立執行緒與結束執行緒
(1) 如何建立執行緒? --- pthread_create() --- man 3 pthread_create
功能: create a new thread
//標頭檔案
#include <pthread.h>
//函式原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
thread: 執行緒的ID號 pthread_t是執行緒ID號資料型別
attr: 執行緒屬性 NULL -> 普通屬性 非NULL --> 額外屬性,例如分離屬性
start_routine: 執行緒的例程函式
arg: 主執行緒傳遞子執行緒的額外引數(也就是函式routine的形參)
//返回值:
成功:0
失敗:錯誤碼 非0
Compile and link with -pthread. --> 只要工程中涉及到了執行緒的函式,在編譯時都要連結 -lpthread
幾種情況可以讓子執行緒退出:
1) 使用pthread_exit() --> 提前子執行緒退出
2) 執行子執行緒例程函式的return 0; --> 子執行緒正常退出
3) 主執行緒被執行了exit(0) --> 整個程式都退出了,所以無論子執行緒在處理什麼事情,都馬上退出
4) 主執行緒呼叫pthread_cancel主動取消子執行緒
例子1:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
void* routine(void *arg)
{
int i;
for(i=5;i>0;i--)
{
printf("child i = %d\n",i);
sleep(1);
}
return 0;
}
int main()
{
//主執行緒
printf("before,I am main thread!\n");
//建立子執行緒
pthread_t tid;
int ret,i;
ret = pthread_create(&tid,NULL,routine,NULL);
if(ret != 0)
{
printf("pthread_create error!\n");
exit(1);
}
printf("after,I am main thread!\n");
for(i=10;i>0;i--)
{
printf("parent i = %d\n",i);
sleep(1);
}
return 0;
}
(2)接合執行緒 --- 回收子執行緒資源pthread_join()
功能: join with a terminated thread 結合執行緒
//標頭檔案
#include <pthread.h>
//函式原型
int pthread_join(pthread_t thread, void **retval);
thread: 需要結合的執行緒的TID號
retval: 儲存子執行緒退出值的指標 NULL --> 不關注子執行緒的退出狀態
(也就是執行緒推出時返回值的指標)
返回值:
成功: 0
失敗: 錯誤碼 非0
(3)執行緒的退出 --- pthread_exit()
//標頭檔案
#include <pthread.h>
void pthread_exit(void *retval);
retval: 子執行緒退出值,退出值不能是區域性變數
返回值:
沒有返回值!
例子2:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
int state = 5;
void* routine(void *arg)
{
printf("I am child!\n");
pthread_exit((void *)&state);
}
int main()
{
printf("before create!\n");
void *p = NULL;
pthread_t tid;
pthread_create(&tid,NULL,routine,NULL);
printf("after create!\n");
//阻塞地結合子執行緒
pthread_join(tid,&p);
printf("exit state = %d\n",*(int *)p);
return 0;
}
(4)執行緒屬性執行緒屬性 --- 分離屬性
分離屬性: 主執行緒中不需要呼叫pthread_join去結合子執行緒
非分離屬性: 主執行緒中需要呼叫pthread_join去結合子執行緒
如何建立分離屬性執行緒?
方法一:
屬性型別: pthread_attr_t
1.定義一個屬性變數
pthread_attr_t attr;
2.初始化屬性變數 --- pthread_attr_init() --- man 3 pthread_attr_init
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
attr: 未初始化的屬性變數
//返回值:
成功: 0
失敗: 非0
3.設定屬性變數 --- pthread_attr_setdetachstate() --- man 3 pthread_attr_setdetachstate
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
attr: 已初始化的屬性變數
detachstate: 屬性
PTHREAD_CREATE_DETACHED --> 分離屬性 --> 不需要pthread_join(),就算你pthread_join()也沒有用
PTHREAD_CREATE_JOINABLE --> 非分離屬性 --> 需要pthread_join(),如果不呼叫pthread_join(),那麼主執行緒就會提前退出而導致子執行緒也提前退出。
//返回值:
成功: 0
失敗: 非0
4.用attr屬性變數建立一個新的執行緒,那麼這個執行緒就是分離屬性
pthread_create(&tid,&attr,routine,NULL);
5.銷燬屬性變數
//標頭檔案
#include <pthread.h>
//函式原型
int pthread_attr_destroy(pthread_attr_t *attr);
attr: 已初始化的屬性變數
//返回值:
成功: 0
失敗: 非0
例子:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void* routine(void *arg) //這是一個分離的子執行緒
{
int i;
for(i=10;i>0;i--)
{
sleep(1);
printf("child i = %d\n",i);
}
pthread_exit(NULL);
}
int main()
{
//1. 定義屬性變數
pthread_attr_t attr;//這時attr是一個未初始化的屬性變數
//2. 初始化屬性變數
pthread_attr_init(&attr); //attr是已初始化屬性變數
//但是attr這個屬性變數中沒有任何的屬性
//3. 設定一個分離屬性給attr
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
//4. 使用attr這個帶有分離屬性的變數建立執行緒
pthread_t tid;
pthread_create(&tid,&attr,routine,NULL);
//5. 主執行緒任務只執行5秒鐘
int i;
for(i=5;i>0;i--)
{
sleep(1);
printf("parent i = %d\n",i);
}
//1. 如果執行緒是分離屬性的,主執行緒有沒有pthread_join(tid,NULL);
// 結果都是5秒後,子執行緒就會因為主執行緒的退出而退出
//2. 如果執行緒是非分離屬性,主執行緒如果有呼叫pthread_join(tid,NULL);
// 那麼主執行緒就會去等待子執行緒的任務完成後,才退出
//3. 如果執行緒是非分離屬性,主執行緒如果沒有呼叫pthread_join(tid,NULL);
// 結果是子執行緒因為主執行緒提前退出而退出
//4. 總的一句話,不管子執行緒是不是分離的,如果主執行緒退出了,子執行緒就一定會退出!
pthread_join(tid,NULL);
//6. 銷燬屬性變數
pthread_attr_destroy(&attr);
return 0;
}
方法二:
線上程內部呼叫 pthread_detach() --> 使得自己變成分離的屬性
#include <pthread.h>
int pthread_detach(pthread_t thread);
thread: 需要設定分離屬性的TID號
//返回值:
成功: 0
失敗: 錯誤碼
獲取執行緒的ID號 --- pthread_self() --- man 3 pthread_self
#include <pthread.h>
pthread_t pthread_self(void);
//返回值: 返回執行緒自身的ID號
例子:
void* routine(void *arg) //本來是非分離
{
pthread_detach(pthread_self()); //變成分離
}
int main()
{
...;
pthread_create(&tid,NULL,routine,NULL);
}
(5)執行緒的取消 ---- pthread_cancel()
send a cancellation request to a thread
#include <pthread.h>
int pthread_cancel(pthread_t thread);
thread: 需要進行取消的執行緒的ID號
//返回值:
成功: 0
失敗: 錯誤碼
例子:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void* routine(void *arg)
{
int i;
for(i=5;i>0;i--)
{
printf("i = %d\n",i);
sleep(1);
}
pthread_exit(NULL);
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,routine,NULL); //預設是立即取消,能夠響應取消
sleep(2);
//pthread_cancel(tid);
printf("parent send cancel to child!\n");
pthread_join(tid,NULL);
return 0;
}
(6)設定執行緒的取消響應行為
1)設定取消響應的使能: 響應取消 / 不響應取消 --- pthread_setcancelstate()
執行緒建立後,預設是響應取消
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
state: 使能狀態
PTHREAD_CANCEL_ENABLE --> 使能取消
PTHREAD_CANCEL_DISABLE --> 不使能取消
oldstate: 原來的狀態,不想儲存則填NULL
//返回值:
成功: 0
失敗: 錯誤碼非0
2)設定取消響應的立即取消,還是延遲取消! -- 取消點
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
type:
PTHREAD_CANCEL_DEFERRED --> 延遲取消
PTHREAD_CANCEL_ASYNCHRONOUS --> 立即取消
oldtype: 原來的狀態,不想儲存則填NULL
//返回值:
成功: 0
失敗: 錯誤碼非0
延遲取消: 遇到取消點才取消
例子:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void* routine(void *arg)
{
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
long i,j;
for(j=0;j<100000;j++)
{
for(i=0;i<100000;i++)
{
}
}
fprintf(stderr,"%c",'a');//執行完取消點這句話,就馬上響應取消的訊號
fprintf(stderr,"%c",'h');
fprintf(stderr,"%c",'j');
pthread_exit(NULL);
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,routine,NULL); //預設是立即取消,能夠響應取消
pthread_cancel(tid);
printf("parent send cancel to child!\n");
pthread_join(tid,NULL);
return 0;
}
(7)執行緒的取消例程函式pthread_cleanup_push()、pthread_cleanup_pop()
1、壓棧: --- pthread_cleanup_push() --- man 3 pthread_cleanup_push
作用:是將routine()函式進行壓棧,這些函式會以棧的形式進行儲存(也就是當呼叫這些函式時最後進去的函式是第一個執行)
#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *),void *arg);
routine: 例程函式指標 --> 將來收到取消請求,首先會執行這個函式先
arg: 傳遞給函式的額外的引數
返回值:沒有返回值!
2、彈棧: ----- pthread_cleanup_pop --- man 3 pthread_cleanup_pop
作用:就是將壓棧的函式進行出棧
#include <pthread.h>
void pthread_cleanup_pop(int execute);
execute: 0 --> 不執行(將routine從棧中彈出來,但不執行它)
非0 --> 執行
返回值:沒有返回值!
總結: 壓棧和彈棧兩個函式必須同時出現,必須在同一個程式碼塊中。執行原理:在兩個函式之間該執行緒如果出現異常退出(除了本執行緒用了return,其他形式推出執行緒都為異常退出,例如pthread_cancel()、pthread_exit()都屬於異常退出),則呼叫執行棧裡的函式後結束執行緒,否則繼續往後執行執行pthread_cleanup_pop()對棧裡的函式程式出棧,當pthread_cleanup_pop()的引數為0時,則彈棧時不執行函式,如果是1,則執行函式的內容。
例子:
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
void fun(void *arg)
{
int fd = *(int *)arg;
close(fd);
printf("I rcv a signal!\n");
}
void* routine(void *arg)
{
int fd = open("1.txt",O_RDWR);
pthread_cleanup_push(fun,(void *)&fd);
int i;
for(i=5;i>0;i--)
{
printf("i = %d\n",i);
sleep(1);
}
pthread_cleanup_pop(1);//執行函式fun
pthread_cleanup_pop(0);//不執行函式fun
close(fd);
pthread_exit(NULL);
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,routine,NULL);
//sleep(2);
//當有這一句時,無論pthread_clean_pop()函式引數是什麼,都會執行fun函式,因為此時執行緒屬於異常退出
//pthread_cancel(tid);
pthread_join(tid,NULL);
return 0;
}
2、執行緒的通訊
(1)無名訊號量 sem_init()、sem_post()、sem_wait()、sem_destroy()
既可以作用程式,也可以作用執行緒。
無名訊號量由於沒有名字,所以不能開啟,只能初始化!
1. 初始化無名訊號量 --- sem_init() --- man 3 sem_init
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem: 訊號量的地址
pshared: 0 --> 作用於執行緒 非0 --> 作用於程式之間
value: 訊號量的起始值
//返回值:
成功: 0
失敗: -1
2. 資源數修改
資源數加1: sem_post()
資源數減1: sem_wait()
#include <semaphore.h>
int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);
sem: 訊號量的地址
//返回值:
成功:0
失敗:-1
3. 銷燬無名訊號量 --- sem_destroy -- man 3 sem_destroy
#include <semaphore.h>
int sem_destroy(sem_t *sem);
sem: 必須是初始化過訊號量的地址
//返回值:
成功:0
失敗:-1
例子:
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <semaphore.h>
sem_t sem;
void* routine(void *arg)
{
//剛開始是1的,看看哪個執行緒比較快,先把1變成0
sem_wait(&sem);
printf("TID = %d\n",(int)pthread_self());
char buf[] = "helloworld!";
int i;
for(i=0;buf[i]!='\0';i++)
{
fprintf(stderr,"%c",buf[i]);
sleep(1);
}
//這個執行緒任務完成了,把資源數從0變成1
sem_post(&sem);
pthread_exit(NULL);
}
int main()
{
pthread_t tid[5]; //5個ID號
int i;
sem_init(&sem,0,1);
//1. 產生5個子執行緒
for(i=0;i<5;i++)
{
pthread_create(&tid[i],NULL,routine,NULL);
}
//2. 結合執行緒
for(i=0;i<5;i++)
{
pthread_join(tid[i],NULL);
}
sem_destroy(&sem);
return 0;
}
(2)互斥鎖pthread_mutex_init()、pthread_mutex_lock()、pthread_mutex_unlock()、pthread_mutex_destroy()
1. 初始化鎖 -- 兩種初始化的結果都是一樣
1.1)動態初始化 -- 呼叫介面 pthread_mutex_init() --- man 3 pthread_mutex_init
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *attr);
mutex: 未初始化鎖變數的地址
attr: 鎖的屬性 普通屬性: NULL
//返回值:
成功: 0
失敗: -1
1.2)靜態初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; --> 普通屬性的鎖
2.上鎖 --> 當執行緒需要訪問臨界資源時,主動上鎖,保證執行緒同步互斥問題
pthread_mutex_lock --- man 3 pthread_mutex_lock
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
mutex: 已初始化鎖的地址
//返回值:
成功:0
失敗:錯誤碼
3.解鎖 --> 當執行緒訪問完臨界資源時,主動釋放資源,不然別的執行緒就沒法上鎖
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
mutex: 已初始化鎖的地址
//返回值:
成功:0
失敗:錯誤碼
4. 銷燬鎖 --- pthread_mutex_destroy --- man 3 pthread_mutex_destroy
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
mutex: 已初始化鎖的地址
//返回值:
成功:0
失敗:錯誤碼
例子:要求2個執行緒,同時搶佔任務,任務: 每隔1秒列印helloworld一個字元,如果在上鎖的情況下,收到取消請求,還可以解鎖。
#include <pthread.h>
#include <stdio.h>
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 靜態初始化
pthread_mutex_t m;
void handler(void *arg)
{
pthread_mutex_unlock(&m);
}
void* routine(void *arg)
{
pthread_mutex_lock(&m);
pthread_cleanup_push(handler,NULL);
char buf[] = "helloworld!";
int i;
for(i=0;buf[i]!='\0';i++)
{
fprintf(stderr,"%c",buf[i]);
sleep(1);
}
pthread_mutex_unlock(&m);
pthread_cleanup_pop(0);
pthread_exit(NULL);
}
int main()
{
//1. 初始化鎖
pthread_mutex_init(&m,NULL);
//2. 建立執行緒
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,routine,NULL);
pthread_create(&tid2,NULL,routine,NULL);
sleep(2);
pthread_cancel(tid2);
//3. 結合
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
//4. 銷燬鎖
pthread_mutex_destroy(&m);
return 0;
}
(3)讀寫鎖pthread_rwlock_init()、thread_rwlock_rdlock()、pthread_rwlock_wrlock()、pthread_rwlock_unlock()、pthread_rwlock_destroy()
讀寫鎖意義
讀鎖(共享鎖) --> 訪問臨界資源,多個執行緒可以重複上鎖(不存在一定要解鎖了才能上鎖的情況) 不會阻塞
寫鎖(互斥鎖) --> 執行緒如果需要修改臨界資源 --> 寫鎖不能同時上,會阻塞
1.初始化讀寫鎖 --- pthread_rwlock_init() --- man 3 pthread_rwlock_init
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t* rwlock,
const pthread_rwlockattr_t* attr);
rwlock: 讀寫鎖變數的地址
attr: 讀寫鎖變數的屬性 普通屬性: NULL
//返回值:
成功:0
失敗:錯誤碼
2. 讀鎖上鎖(多個執行緒可以同時上鎖) --- pthread_rwlock_rdlock() --- man 3 pthread_rwlock_rdlock
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
rwlock: 已初始化讀寫鎖變數的地址
//返回值:
成功:0
失敗:錯誤碼
3. 寫鎖上鎖(不能同時上鎖) --- pthread_rwlock_wrlock() --- man 3 pthread_rwlock_wrlock
#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
rwlock: 已初始化讀寫鎖變數的地址
//返回值:
成功:0
失敗:錯誤碼
4. 讀寫鎖解鎖(無論是讀鎖還是寫鎖,解鎖函式都是一樣的) -- pthread_rwlock_unlock()
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
rwlock: 已初始化讀寫鎖變數的地址,鎖一定是正在上鎖的狀態
//返回值:
成功:0
失敗:錯誤碼
5. 銷燬讀寫鎖 --- pthread_rwlock_destroy() --- man 3 pthread_rwlock_destroy
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
rwlock: 已初始化讀寫鎖變數的地址
//返回值:
成功:0
失敗:錯誤碼
例子:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
int a = 100;
pthread_rwlock_t m;
void* routine4(void *arg)
{
//1. 上讀鎖
pthread_rwlock_wrlock(&m);
sleep(3);
a = 30;
printf("I am thread4: %d\n",a);
//2. 解讀鎖
pthread_rwlock_unlock(&m);
pthread_exit(NULL);
}
void* routine3(void *arg)
{
//1. 上讀鎖
pthread_rwlock_wrlock(&m);
sleep(5);
a = 50;
printf("I am thread3: %d\n",a);
//2. 解讀鎖
pthread_rwlock_unlock(&m);
pthread_exit(NULL);
}
void* routine2(void *arg)
{
//1. 上讀鎖
pthread_rwlock_rdlock(&m);
sleep(2);
printf("I am thread2: %d\n",a);
//2. 解讀鎖
pthread_rwlock_unlock(&m);
pthread_exit(NULL);
}
void* routine1(void *arg)
{
//1. 上讀鎖
pthread_rwlock_rdlock(&m);
sleep(3);
printf("I am thread1: %d\n",a);
//2. 解讀鎖
pthread_rwlock_unlock(&m);
pthread_exit(NULL);
}
void* get_time(void *arg)
{
int i;
for(i=0;i<100;i++)
{
printf("%d\n",i);
sleep(1);
}
}
int main()
{
//0. 初始化讀寫鎖
pthread_rwlock_init(&m,NULL);
//1. 建立4個執行緒
//2個執行緒讀取變數的值,2個執行緒修改值
pthread_t tid1,tid2,tid3,tid4,tid;
pthread_create(&tid1,NULL,routine1,NULL); //讀取變數,但是我要讀取3秒鐘才讀完
pthread_create(&tid2,NULL,routine2,NULL); //讀取變數,但是我要讀取3秒鐘才讀完
pthread_create(&tid3,NULL,routine3,NULL); //修改變數
pthread_create(&tid4,NULL,routine4,NULL); //修改變數
pthread_create(&tid,NULL,get_time,NULL);
//3. 結合
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
pthread_join(tid4,NULL);
//4. 銷燬讀寫鎖
pthread_rwlock_destroy(&m);
return 0;
}
(4)條件變數pthread_cond_init()、pthread_cond_wait()、pthread_cond_broadcast()、pthread_cond_signal()、pthread_cond_destroy()
條件變數: 執行緒處理任務 --> 有任務 --> 處理任務
--> 沒有任務 --> 先讓執行緒進入睡眠 --> 條件變數 --> 等到有任務時,再喚醒執行緒去處理任務。
條件變數一般與互斥鎖一起使用
1. 初始化條件變數(跟屬性變數,互斥鎖變數,無名訊號量類似,都需要初始化)
1)動態初始化
pthread_cond_t是條件變數的資料型別
#include <pthread.h>
int pthread_cond_init(pthread_cond_t * cond,
const pthread_condattr_t * attr);
cond: 未初始化條件變數的地址
attr: 條件變數的屬性 NULL
//返回值:
成功:0
失敗:錯誤碼
2)靜態初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //普通屬性
2. 進入條件變數中等待 --- pthread_cond_wait() ---> 自動解鎖,進入條件變數(不需要使用者自己解鎖)
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t * cond,
pthread_mutex_t * mutex);
cond: 已初始化的條件變數
mutex: 已初始化並且已經上鎖的互斥鎖變數
//返回值:
成功:0
失敗:錯誤碼
3. 喚醒條件變數上的等待的執行緒 -- 自動上鎖
廣播: --> 喚醒所有的執行緒 --- pthread_cond_broadcast()
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
單播: --> 喚醒隨機一條執行緒 --- pthread_cond_signal()
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
cond: 已初始化的條件變數
//返回值:
成功:0
失敗:錯誤碼
4. 銷燬條件變數 --- pthread_cond_destroy() --- man 3 pthread_cond_destroy
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
cond: 已初始化的條件變數
//返回值:
成功:0
失敗:錯誤碼
例子:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
int sum = 0; //臨界資源
pthread_mutex_t m;//互斥鎖
pthread_cond_t v;//條件變數
void* routine(void *arg)
{
pthread_mutex_lock(&m);
while(sum < 200) //一定要用while,因為喚醒之後,還需要進行判斷!
{
//不能拿到錢,判斷之後還是進入條件變數中等待
pthread_cond_wait(&v,&m); //自動解鎖,進入條件變數中等待
}
//能拿到錢就執行到這裡
printf("%d:sum = %d\n",(int)pthread_self(),sum); //當前sum值
sum -= 200;
pthread_mutex_unlock(&m);
pthread_exit(NULL);
}
int main()
{
//0. 初始化鎖與條件變數
pthread_mutex_init(&m,NULL);
pthread_cond_init(&v,NULL);
//1. 建立10個子執行緒
pthread_t tid[10];
int i;
for(i=0;i<10;i++)
{
pthread_create(&tid[i],NULL,routine,NULL);
}
sleep(3);
//只要程式碼中需要修改臨界資源的值,都一定要上鎖
pthread_mutex_lock(&m);
sum = 1000; //父母打錢
printf("sum += 1000!\n");
pthread_cond_broadcast(&v);
pthread_mutex_unlock(&m);
pause();
}
(5)執行緒池
執行緒池:
函式介面是使用者自己寫,而不是在Linux中自帶(不能通過man手冊去查詢)
執行緒池(公司)是使用執行緒(員工)的擴充
一. 執行緒池的模型
互斥鎖lock: 訪問臨界資源(任務佇列)時,必須使用互斥進行同步互斥!
條件變數cond:當某些執行緒沒有任務時,那麼自動解鎖,進入條件變數中進行等待
任務佇列task_list(臨界資源):每一個任務都被看作一個節點,所有的任務使用一個單向連結串列連線在一起
執行緒的ID號tids: 執行緒池中有多少個執行緒,用於存放執行緒的TID號
等待的任務個數waiting_task: 當前任務佇列還有多少個任務沒做的!
執行緒中執行緒的總數active_threads: 執行緒的總數(可以新增)
執行緒的開關shutdown: 布林型別變數 真 -> 執行緒池正在使用 假 -> 執行緒池中所有執行緒已經退出
//執行緒池結構體
typedef struct thread_pool
{
pthread_mutex_t lock;
pthread_cond_t cond;
struct task *task_list;
pthread_t *tids;
unsigned max_waiting_tasks;
unsigned waiting_tasks;
unsigned active_threads;
bool shutdown;
}thread_pool;
//任務節點的結構體
struct task
{
//資料域
void *(*do_task)(void *arg);
//任務節點的處理函式
void *arg;
//需要額外傳遞的引數
//指標域
struct task *next;
//指向下一個任務節點
};
二. 執行緒池介面
1. 初始化執行緒池 -- init_pool() --- 給執行緒池結構體賦值
#include "thread_pool.h"
bool init_pool(thread_pool *pool, unsigned int threads_number);
pool: 執行緒池變數的地址
threads_number: 初始化執行緒的個數
返回值:
成功: true
失敗: false
實現過程:
//初始化執行緒池中的鎖變數
pthread_mutex_init(&pool->lock, NULL);
//初始化執行緒池中的條件變數
pthread_cond_init(&pool->cond, NULL);
//初始化標誌,代表執行緒池正在執行
pool->shutdown = false;
//為任務連結串列頭節點申請記憶體,指標域初始化(任務佇列中頭節點無效)
pool->task_list = malloc(sizeof(struct task));
pool->task_list->next = NULL;
//申請存放執行緒TID號記憶體
pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);
#define MAX_ACTIVE_THREADS 20
//最大任務數初始化
pool->max_waiting_tasks = MAX_WAITING_TASKS;
#define MAX_WAITING_TASKS 1000
//等待處理的任務數
pool->waiting_tasks = 0;
//初始化執行緒個數
pool->active_threads = threads_number;
//建立執行緒
pthread_create(&((pool->tids)[i]), NULL,routine,(void *)pool);
因為執行緒在處理任務時使用了互斥鎖與條件變數,所以在建立執行緒時,
需要把整個初始化過的執行緒池變數傳遞過去!
2. 新增任務節點 --- add_task() -- 生產者!
#include "thread_pool.h"
bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *task);
pool: 已經初始化過的執行緒池變數
do_task: 任務節點的任務函式指標
task: 傳遞給任務函式的額外引數
返回值:
成功: true
失敗: false
實現過程:
(1).申請新節點的記憶體
(2). 為新節點的資料域與指標域賦值
(3). 達到任務的數目最大值
//如果任務已經達到最大值,則不能新增任務,解鎖走人!
if(pool->waiting_tasks >= MAX_WAITING_TASKS)//1000
{
//解鎖
pthread_mutex_unlock(&pool->lock);
//輸出錯誤資訊
fprintf(stderr, "too many tasks.\n");
//釋放剛申請的任務節點的記憶體
free(new_task);
return false;
}
(4). 訪問任務佇列時,都等價於訪問臨界資源,必須需要上鎖!
(5). 單播,喚醒在條件變數中等待的執行緒
pthread_cond_signal(&pool->cond);
3. 新增執行緒池中的執行緒個數 --- add_thread()
#include "thread_pool.h"
int add_thread(thread_pool *pool, unsigned int additional_threads_number);
pool: 已經初始化過的執行緒池變數
additional_threads_number: 想新增的執行緒的數量
返回值:
成功: 真正新增到執行緒池的執行緒數目
失敗: 0 --> additional_threads_number傳遞的值為0
-1 --> 建立新的執行緒失敗
實現過程:
for(i = pool->active_threads;
//執行緒池現有的執行緒的個數
i < total_threads && i < MAX_ACTIVE_THREADS; //小於新增後的總數
i++)
//現有的執行緒新增了actual_increment數量
pool->active_threads += actual_increment;
4. 刪除執行緒池中的執行緒 -- remove_thread()
#include "thread_pool.h"
int remove_thread(thread_pool *pool, unsigned int removing_threads_number);
pool: 已經初始化過的執行緒池變數
additional_threads_number: 想刪除的執行緒的數量
返回值:
成功: 當前執行緒池中剩餘執行緒的個數
失敗: -1
實現過程:
//執行緒池中剩餘的執行緒數,不能為0或者負數,最低是1條
int remaining_threads = pool->active_threads - removing_threads;
remaining_threads = remaining_threads > 0 ? remaining_threads : 1;
當前: 10條執行緒 刪除0條 remaining_threads:10 --> 10
當前: 10條執行緒 刪除5條 remaining_threads:5 --> 5
當前: 10條執行緒 刪除10條 remaining_threads:0 --> 1
當前: 10條執行緒 刪除20條 remaining_threads:-10 --> 1
5. 銷燬執行緒池 -- destroy_pool()
#include "thread_pool.h"
bool destroy_pool(thread_pool *pool);
pool: 已經初始化過的執行緒池變數
返回值:
成功: true
失敗: false
實現過程:
(1).標誌位為真
pool->shutdown = true;
(2). 叫醒所有在條件變數中等待的執行緒,讓判斷標誌位之後,退出執行緒
pthread_cond_broadcast(&pool->cond);
(3). 執行緒判斷標誌位是否為true
if(pool->waiting_tasks == 0 && pool->shutdown == true)
pthread_exit(NULL);
(4). 主執行緒回收資源pthread_join
(5). //釋放所有的記憶體
free(pool->task_list);
free(pool->tids);
free(pool);
6. 執行緒的例程函式 -- 專門用於消費任務的介面
void *routine(void *arg)
實現的過程:
//取出任務節點p
p = pool->task_list->next;
pool->task_list->next = p->next;
防止任務連結串列斷開!
//在任務處理期間,不響應任何的取消訊號
//如果執行緒在處理任務期間收到取消請求,先完成任務,再響應取消
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
//執行任務函式
(p->do_task)(p->arg);
//處理完任務,可響應取消請求
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
例子:(在電腦F:\培訓2資料\01 系統程式設計\06\code\pool_test1)
thread_pool.h
#ifndef _THREAD_POOL_H_
#define _THREAD_POOL_H_
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <pthread.h>
#define MAX_WAITING_TASKS 1000
#define MAX_ACTIVE_THREADS 20
struct task
{
void *(*do_task)(void *arg);
void *arg;
struct task *next;
};
//執行緒狀態的結構體
typedef struct thread_pool
{
pthread_mutex_t lock;
pthread_cond_t cond;
bool shutdown;
struct task *task_list;
pthread_t *tids;
unsigned max_waiting_tasks;
unsigned waiting_tasks;
unsigned active_threads;
}thread_pool;
bool init_pool(thread_pool *pool, unsigned int threads_number);
bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *task);
int add_thread(thread_pool *pool, unsigned int additional_threads_number);
int remove_thread(thread_pool *pool, unsigned int removing_threads_number);
bool destroy_pool(thread_pool *pool);
void *routine(void *arg);
#endif
thread_pool.c
#include "thread_pool.h"
//壓棧例程
void handler(void *arg)
{
printf("[%u] is ended.\n",
(unsigned)pthread_self());
//如果被取消時是上鎖狀態,即在取消前執行該例程解鎖,再執行取消響應!
pthread_mutex_unlock((pthread_mutex_t *)arg);
}
//執行緒專門消費任務連結串列中的任務
void* routine(void *arg)
{
//執行緒池變數傳參
thread_pool *pool = (thread_pool *)arg;
struct task *p;//用於指向將要消費的任務節點
while(1)
{
//防止執行緒在處理任務時,被取消,pthread_cond_wait()是取消點
pthread_cleanup_push(handler, (void *)&pool->lock);
//訪問任務連結串列時,首先上鎖
pthread_mutex_lock(&pool->lock);
//================================================//
// 1, 任務佇列中沒有任務,而且執行緒池未被關閉,執行緒就進入條件變數睡眠等待
while(pool->waiting_tasks == 0 && !pool->shutdown)
{
//自動解鎖,進入條件變數中等待
pthread_cond_wait(&pool->cond, &pool->lock);
}
//注意,這裡不能使用if來判斷,因為執行緒醒來後,還是要詢問有沒有任務!
// 2, 任務佇列中沒有任務,而且執行緒池關閉,即退出走人!
if(pool->waiting_tasks == 0 && pool->shutdown == true)
{
//解鎖
pthread_mutex_unlock(&pool->lock);
//走人
pthread_exit(NULL); // CANNOT use 'break';
}
// 3, 有任務,執行緒處理它!
//取出任務節點p
p = pool->task_list->next;
pool->task_list->next = p->next;
//任務數量減少1
pool->waiting_tasks--;
//================================================//
//訪問完任務連結串列,取得節點,解鎖!
pthread_mutex_unlock(&pool->lock);
//彈棧,不執行例程
pthread_cleanup_pop(0);
//================================================//
//在任務處理期間,不響應任何的取消訊號
//如果執行緒在處理任務期間收到取消請求,先完成任務,再響應取消
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
//執行任務函式
(p->do_task)(p->arg);
//處理完任務,可響應取消請求
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
//釋放處理完的任務節點記憶體
free(p);
}
pthread_exit(NULL);
}
//初始化執行緒池
bool init_pool(thread_pool *pool, unsigned int threads_number)
{
pthread_mutex_init(&pool->lock, NULL);//初始化鎖
pthread_cond_init(&pool->cond, NULL);//初始化條件變數
pool->shutdown = false;//初始化標誌 //正在使用
pool->task_list = malloc(sizeof(struct task));//為任務連結串列頭節點申請記憶體
pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);//申請存放執行緒TID號記憶體
//錯誤判斷
if(pool->task_list == NULL || pool->tids == NULL)
{
perror("allocate memory error");
return false;
}
//任務連結串列節點指標域初始化
pool->task_list->next = NULL;
pool->max_waiting_tasks = MAX_WAITING_TASKS;//最大任務數初始化
pool->waiting_tasks = 0;//等待處理的任務數
pool->active_threads = threads_number;//初始化執行緒個數
int i;
//初始化幾條執行緒,就建立多少條執行緒
for(i=0; i<pool->active_threads; i++)
{
//普通屬性執行緒,例程routine
if(pthread_create(&((pool->tids)[i]), NULL,
routine, (void *)pool) != 0)
{
perror("create threads error");
return false;
}
}
return true;
}
//新增任務
bool add_task(thread_pool *pool,
void *(*do_task)(void *arg), void *arg)
{
//申請新任務節點的記憶體
struct task *new_task = malloc(sizeof(struct task));
if(new_task == NULL)
{
perror("allocate memory error");
return false;
}
//初始化新任務節點成員,給資料域賦值
new_task->do_task = do_task;
new_task->arg = arg;
new_task->next = NULL; //尾插,最後一個點
//只要訪問任務連結串列,就要上鎖
//插入該節點到任務連結串列尾部
//============ LOCK =============//
pthread_mutex_lock(&pool->lock);
//===============================//
//如果任務已經達到最大值,則不能新增任務,解鎖走人!
if(pool->waiting_tasks >= MAX_WAITING_TASKS)
{
//解鎖
pthread_mutex_unlock(&pool->lock);
//輸出錯誤資訊
fprintf(stderr, "too many tasks.\n");
//釋放剛申請的任務節點的記憶體
free(new_task);
return false;
}
//尋找最尾的節點tmp
struct task *tmp = pool->task_list;
while(tmp->next != NULL)
tmp = tmp->next;
//迴圈結束時,tmp指向任務連結串列的最後一個節點
//讓最後的節點的指標域指向新任務節點
tmp->next = new_task;
//任務數量增加1
pool->waiting_tasks++;
//新增完畢,訪問完畢,解鎖!
//=========== UNLOCK ============//
pthread_mutex_unlock(&pool->lock);
//===============================//
//新增了一個任務,喚醒其中一條在條件變數中睡眠的執行緒起來處理任務。
pthread_cond_signal(&pool->cond);
return true;
}
//新增執行緒
int add_thread(thread_pool *pool, unsigned additional_threads)
{
//如果新增的數目為0,馬上返回!
if(additional_threads == 0)
return 0;
//總的執行緒數 = 現有的執行緒數 + 新新增的數目
unsigned total_threads = //8
pool->active_threads + additional_threads;
//5 //3
//actual_increment為實際建立成功的執行緒數
int i, actual_increment = 0;
//建立新新增的執行緒數
for(i = pool->active_threads; //5
i < total_threads && i < MAX_ACTIVE_THREADS;
i++) //8
{
if(pthread_create(&((pool->tids)[i]),
NULL, routine, (void *)pool) != 0)
{
perror("add threads error");
// 沒有建立到任何的執行緒,馬上返回!
if(actual_increment == 0)
return -1;
break;
}
//每建立一條,actual_increment就增加1
actual_increment++;
}
//現有的執行緒新增了actual_increment數量
pool->active_threads += actual_increment;
//返回真正新增的執行緒數量
return actual_increment;
}
//刪除執行緒,返回剩餘的執行緒數 //5 10
int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
//如果刪除0條,馬上返回!
if(removing_threads == 0)
return pool->active_threads;//當前執行緒池還有多少個執行緒
//執行緒池中剩餘的執行緒數,不能為0或者負數,最低是1條
int remaining_threads = pool->active_threads - removing_threads;
remaining_threads = remaining_threads > 0 ? remaining_threads : 1;
int i;
//tid陣列下標需要減1 10 tid[9] 5 tid[4]
for(i=pool->active_threads-1; i>remaining_threads-1; i--)
{
//減少相應的執行緒數目
errno = pthread_cancel(pool->tids[i]);
//取消失敗,馬上break,不再取消
if(errno != 0)
break;
}
//沒有取消到任何一條執行緒
if(i == pool->active_threads-1)
return -1;
//有取消到執行緒,但是取消不完全
else
{
//i為陣列下標,需要+1得到剩餘的執行緒數
pool->active_threads = i+1;
return i+1;
}
}
//銷燬執行緒池
bool destroy_pool(thread_pool *pool)
{
// 1, activate all threads
//執行緒池的關閉標誌為真
pool->shutdown = true;
//喚醒所有的執行緒,讓他們醒來判斷執行緒的標誌為true,全部退出
pthread_cond_broadcast(&pool->cond);
// 2, wait for their exiting
int i;
//等待執行緒退出
for(i=0; i<pool->active_threads; i++)
{
//等待回收
errno = pthread_join(pool->tids[i], NULL);
//執行緒退出失敗
if(errno != 0)
{
printf("join tids[%d] error: %s\n",
i, strerror(errno));
}
//執行緒退出成功
else
printf("[%u] is joined\n", (unsigned)pool->tids[i]);
}
// 3, free memories
//釋放所有的記憶體
free(pool->task_list);
free(pool->tids);
free(pool);
return true;
}
main.c
#include "thread_pool.h"
void *mytask(void *arg)
{
int n = (int)arg;
//工作任務:餘數是多少,就睡多少秒,睡完,任務就算完成
printf("[%u][%s] ==> job will be done in %d sec...\n",
(unsigned)pthread_self(), __FUNCTION__, n);
sleep(n);
printf("[%u][%s] ==> job done!\n",
(unsigned)pthread_self(), __FUNCTION__);
return NULL;
}
void *count_time(void *arg)
{
int i = 0;
while(1)
{
sleep(1);
printf("sec: %d\n", ++i);
}
}
int main(void)
{
// 本執行緒用來顯示當前流逝的秒數
// 跟程式邏輯無關
pthread_t a;
pthread_create(&a, NULL, count_time, NULL);
// 1, initialize the pool
thread_pool *pool = malloc(sizeof(thread_pool));
init_pool(pool, 2);
//2個執行緒都在條件變數中睡眠
// 2, throw tasks
printf("throwing 3 tasks...\n");
add_task(pool, mytask, (void *)(rand()%10));//5
add_task(pool, mytask, (void *)(rand()%10));//8
add_task(pool, mytask, (void *)(rand()%10));//6
// 3, check active threads number
printf("current thread number: %d\n",
remove_thread(pool, 0));
sleep(9);
// 4, throw tasks
printf("throwing another 6 tasks...\n");
add_task(pool, mytask, (void *)(rand()%10));
add_task(pool, mytask, (void *)(rand()%10));
add_task(pool, mytask, (void *)(rand()%10));
add_task(pool, mytask, (void *)(rand()%10));
add_task(pool, mytask, (void *)(rand()%10));
add_task(pool, mytask, (void *)(rand()%10));
// 5, add threads
add_thread(pool, 2);
sleep(5);
// 6, remove threads
printf("remove 3 threads from the pool, "
"current thread number: %d\n",
remove_thread(pool, 3));
// 7, destroy the pool
destroy_pool(pool);
return 0;
}
makefile
CC = gcc
CFLAGS = -O0 -Wall -g -lpthread
test:main.c thread_pool.c
$(CC) $^ -o $@ $(CFLAGS)
debug:main.c thread_pool.c
$(CC) $^ -o $@ $(CFLAGS) -DDEBUG
clean:
$(RM) .*.sw? test debug *.o
.PHONY:all clean