作者:李春港
出處: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