距離上一次利用高并發技術實作360度行車記錄儀功能已經過去半年了,開始寫一系列關于系統編程和網路編程內容進行總結,
溫故而知新,歡迎大家討論學習,
文章目錄
- 1 守護行程
- 1.1 什么是守護行程
- 1.2 守護行程創建步驟
- 1.3 守護行程代碼實作(重點)
- 2 執行緒
- 2.1 什么是執行緒
- 2.2 執行緒共享資源
- 2.3 執行緒間非共享資源
- 2.4 執行緒的優缺點
- 2.5 執行緒控制原語
- 2.5.1 pthread_self 函式
- 2.5.2 pthread_create 函式
- 2.5.2.1 創建一個新執行緒,列印執行緒 ID(i值傳遞方式)
- 2.5.2.2創建一個新執行緒,列印執行緒 ID(i地址傳遞方式)(錯)
- 2.5.3 pthread_exit 函式
- 2.5.3.1 pthread_exit和exit return比較(重)
- 2.5.3.2 pthread_exit 替代 主執行緒sleep+return (重)
- 2.5.4 pthread_join 函式(重)
- 2.5.4.1 pthread_join 舉例使用
- 2.5.5 pthread_detach 函式
- 2.5.5.1 detach分離后 join出現的情況
- 2.5.6 pthread_cancel 函式
- 2.6 執行緒行程控制原語比對
- 2.7 執行緒屬性注意事項(重)
1 守護行程
1.1 什么是守護行程
- 在linux系統中,我們會發現在系統啟動的時候有很多的行程就已經開始跑了,也稱為服務,這也是我們所說的守護行程,
- 守護行程(daemon)是生存期長的一種行程,沒有控制終端,
- 它們常常在系統引導裝入時啟動,僅在系統關閉時才終止,
- UNIX系統有很多守護行程,守護行程程式的名稱通常以字母“d”結尾:例如,syslogd 就是指管理系統日志的守護行程
- 通過ps行程查看器
ps -efj的輸出實體,內核守護行程的名字出現在方括號中,大致輸出如下:

1.2 守護行程創建步驟
- fork子行程,讓父行程終止,
- 子行程呼叫,setsid()創建會話,
- 通常根據需要,改變作業目錄chdir
- 通常根據需要,重設umask檔案權限掩碼
- 通常根據需要,關閉/重定向檔案描述符
- 守護行程,業務邏輯,while()
1.3 守護行程代碼實作(重點)
主要是理解一些概念,重點參考一下文獻,
參考文獻(非常重要)
1 掩碼+行程組+會話的描述
2 dev/null是什么
3標準輸入 標準輸出 標準錯誤
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
pid_t pid;
int ret, fd;
pid = fork();
if (pid > 0) // 父行程終止
exit(0);
pid = setsid(); //創建新會話
if (pid == -1)
sys_err("setsid error");
ret = chdir("/home/itcast/28_Linux"); // 改變作業目錄位置
if (ret == -1)
sys_err("chdir error");
umask(0022); // 改變檔案訪問權限掩碼
close(STDIN_FILENO); // 關閉檔案描述符 0標準輸入 //因為0 1 2 都是行程啟動默認啟動的
fd = open("/dev/null", O_RDWR); // fd --> 0
if (fd == -1)
sys_err("open error");
dup2(fd, STDOUT_FILENO); // 重定向 stdout和stderr
dup2(fd, STDERR_FILENO);
while (1); // 模擬 守護行程業務.
return 0;
}
2 執行緒
2.1 什么是執行緒
-
輕量級行程(light-weight process),也有 PCB,創建執行緒使用的底層函式和行程一樣,都是 clone
-
從內核里看行程和執行緒是一樣的,都有各自不同的 PCB,但是 PCB 中指向記憶體資源的三級頁表是相同的(下圖區別行程)

-
行程可以蛻變成執行緒
-
執行緒可看做暫存器和堆疊的集合
-
在 linux 下,執行緒最是小的執行單位;行程是最小的分配資源單位

參考:《Linux 內核源代碼情景分析》
對于行程來說,相同的地址(同一個虛擬地址)在不同的行程中,反復使用而不沖突,原因是他們雖虛擬址一樣,但,頁目錄、頁表、物理頁面各不相同,相同的虛擬址,映射到不同的物理頁面記憶體單元,最終訪問不同的物理頁
面,
但!執行緒不同!兩個執行緒具有各自獨立的 PCB,但共享同一個頁目錄,也就共享同一個頁表和物理頁面,所以兩個 PCB 共享一個地址空間,
實際上,無論是創建行程的 fork,還是創建執行緒的 pthread_create,底層實作都是呼叫同一個內核函式 clone,
如果復制對方的地址空間,那么就產出一個“行程”;如果共享對方的地址空間,就產生一個“執行緒”,
因此:Linux 內核是不區分行程和執行緒的,只在用戶層面上進行區分,所以,執行緒所有操作函式 pthread_* 是庫函式,而非系統呼叫
2.2 執行緒共享資源
- 檔案描述符表
- 每種信號的處理方式
- 當前作業目錄
- 用戶 ID 和組 ID
- 記憶體地址空間 (.text/.data/.bss/heap/共享庫)
2.3 執行緒間非共享資源
- 執行緒 id
- 處理器現場和堆疊指標(內核堆疊)
- 獨立的堆疊空間(用戶空間堆疊)
- errno 變數
- 信號屏蔽字
- 調度優先級
2.4 執行緒的優缺點
優點:
- 提高程式并發性
- 開銷小
- 資料通信、共享資料方便
缺點:
- 執行緒不穩定(第三方庫函式實作)
- 執行緒除錯困難
- 等待使用共享資源時造成程式運行速度變慢,主要是一些獨占性的資源
- 執行緒的死鎖,較長時間的等待或者資源競爭造成死鎖
2.5 執行緒控制原語
編譯的時候記得后面
-l pthread畢竟第三方庫實作
2.5.1 pthread_self 函式
作用:
獲取執行緒 ID,其作用對應行程中 getpid() 函式,
pthread_t pthread_self(void);//成功回傳本執行緒id
2.5.2 pthread_create 函式
創建一個新執行緒,起作用,對應行程中fork()函式,
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 成功回傳0,失敗回傳errno- 引數一:表示傳出引數,表示創建的子執行緒id
- 引數二:執行緒屬性,傳NILL表使用默認屬性
- 引數三:函式指標,指向執行緒主函式(執行緒體),該函式運行結束,則執行緒結束,
- 引數四:引數三函式的引數,空傳NULL
2.5.2.1 創建一個新執行緒,列印執行緒 ID(i值傳遞方式)
主執行緒結束,但是行程地址空間還在,其他子執行緒正常執行. 如果main中return,由于其他子執行緒函式空間是在main函式里面,所以main不能提前結束,sleep就是讓子行程中的函式執行完,(重)
注意 pthread_create 第四個引數的傳遞 ,以及后面的強制轉換,可以先去體會一下錯誤寫法(下下方的例子)
# include<stdio.h>
# include<stdlib.h>
# include<string.h>
# include<unistd.h>
# include<errno.h>
# include<pthread.h>
void* fun(void* arg)
{
int i=(int)arg;
sleep(i);
printf("I am %dth thread: pid=%d,tid=%lu\n",i+1,getpid(),pthread_self());
return NULL;
}
void sys_err(const char*str)
{
perror(str);
exit(1);
}
int main()
{
int i;
int ret;
pthread_t tid;
for(i=0;i<5;i++)
{
ret = pthread_create(&tid,NULL,fun,(void*)i);
if(ret!=0)
{
sys_err("pthread_creat error");
}
}
sleep(i);
return 0;
}
2.5.2.2創建一個新執行緒,列印執行緒 ID(i地址傳遞方式)(錯)
從結果很容易看出i的列印出現的問題,那是因為當我們從子行程(函式內)通過i的地址取值的時候,這個i的地址是在父行程堆疊區,這片地址存放的值i可能已經發生了變化,畢竟父行程也一直在執行
- %lu表示輸出無符號長整型整數
- 編譯指令
gcc test.c -o test -l pthread(注意加后面的)- 需要注意的是,不要讓行程先與子執行緒結束,畢竟共享一片記憶體空間(代碼中的sleep)
# include<stdio.h>
# include<stdlib.h>
# include<string.h>
# include<unistd.h>
# include<errno.h>
# include<pthread.h>
void* fun(void* arg)
{
int i=*((int*)arg);
sleep(i);
printf("I am %dth thread: pid=%d,tid=%lu\n",i+1,getpid(),pthread_self());
return NULL;
}
void sys_err(const char*str)
{
perror(str);
exit(1);
}
int main()
{
int i;
int ret;
pthread_t tid;
for(i=0;i<5;i++)
{
ret = pthread_create(&tid,NULL,fun,(void*)&i);
if(ret!=0)
{
sys_err("pthread_creat error");
}
}
sleep(i);
return 0;
}
2.5.3 pthread_exit 函式
作用:
將單個執行緒退出
void pthread_exit(void *retval);引數:retval 表示執行緒退出狀態,通常傳 NULL
2.5.3.1 pthread_exit和exit return比較(重)
直接說結論:
執行緒中,禁止使用 exit 函式,會導致行程內所有執行緒全部退出,
return 在子執行緒是沒有問題的,他是退出到函式呼叫的位置,也算是執行緒結束,畢竟執行緒也就是一個函式呼叫罷了,
pthread_exit 執行緒退出而不是行程退出切記,
舉個例子放下面,注釋部分自己去掉試試就懂了主控執行緒退出時不能 return 或 exit,(重)
# include<stdio.h>
# include<stdlib.h>
# include<string.h>
# include<unistd.h>
# include<errno.h>
# include<pthread.h>
void* fun(void* arg)
{
int i=(int)arg;
if(i==2)
{
//return 0;
//exit(0);
//pthread_exit(NULL);
}
sleep(i);
printf("I am %dth thread: pid=%d,tid=%lu\n",i+1,getpid(),pthread_self());
return NULL;
}
void sys_err(const char*str)
{
perror(str);
exit(1);
}
int main()
{
int i;
int ret;
pthread_t tid;
for(i=0;i<5;i++)
{
ret = pthread_create(&tid,NULL,fun,(void*)i);
if(ret!=0)
{
sys_err("pthread_creat error");
}
}
sleep(i);
return 0;
}
2.5.3.2 pthread_exit 替代 主執行緒sleep+return (重)
主執行緒結束,但是行程地址空間還在,其他子執行緒正常執行. 如果main中return ,由于其他子執行緒函式空間是在main函式里面,所以main不能提前結束,
# include<stdio.h>
# include<stdlib.h>
# include<string.h>
# include<unistd.h>
# include<errno.h>
# include<pthread.h>
void* fun(void* arg)
{
int i=(int)arg;
if(i==2)
{
//return 0;
//exit(0);
//pthread_exit(NULL);
}
sleep(i);
printf("I am %dth thread: pid=%d,tid=%lu\n",i+1,getpid(),pthread_self());
return NULL;
}
void sys_err(const char*str)
{
perror(str);
exit(1);
}
int main()
{
int i;
int ret;
pthread_t tid;
for(i=0;i<5;i++)
{
ret = pthread_create(&tid,NULL,fun,(void*)i);
if(ret!=0)
{
sys_err("pthread_creat error");
}
}
pthread_exit(NULL);
}
2.5.4 pthread_join 函式(重)
作用:
阻塞等待執行緒退出,獲取執行緒退出狀態 其作用,對應行程中 waitpid() 函式
補充:任意執行緒得到其他執行緒的pid都可以回收,沒有父執行緒回收子執行緒的說法,而行程需要父行程回收子行程,
int pthread_join(pthread_t thread, void **retval);成功:0;失敗:錯誤號
2.5.4.1 pthread_join 舉例使用
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
typedef struct {
int a;
int b;
} exit_t;
void *tfn(void *arg)
{
exit_t *ret;
ret = malloc(sizeof(exit_t));
ret->a = 100;
ret->b = 300;
pthread_exit((void *)ret);
}
int main(void)
{
pthread_t tid;
exit_t *retval;
pthread_create(&tid, NULL, tfn, NULL);
/*呼叫pthread_join可以獲取執行緒的退出狀態*/
pthread_join(tid, (void **)&retval); //wait(&status);
printf("a = %d, b = %d \n", retval->a, retval->b);
return 0;
}
2.5.5 pthread_detach 函式
作用:
實作執行緒分離,執行緒結束后,自動釋放資源,無需pthread_join() 回收資源,
int pthread_detach(pthread_t thread);成功:0;失敗:錯誤號
執行緒分離狀態:指定該狀態,執行緒主動與主控執行緒斷開關系,執行緒結束后,其退出狀態不由其他執行緒獲取,而直接自己自動釋放,網路、多執行緒服務器常用,
行程若有該機制,將不會產生僵尸行程,僵尸行程的產生主要由于行程死后,大部分資源被釋放,一點殘留資源仍存于系統中,導致內核認為該行程仍存在,
也可使用 pthread_create 函式參 2(執行緒屬性)來設定執行緒分離,
2.5.5.1 detach分離后 join出現的情況
符合pthread_detach作用,分離獨立,主執行緒無法再等待回收子執行緒資源,
# include<stdio.h>
# include<stdlib.h>
# include<string.h>
# include<unistd.h>
# include<errno.h>
# include<pthread.h>
void* fun(void* arg)
{
int i=(int)arg;
printf("I am %dth thread: pid=%d,tid=%lu\n",i+1,getpid(),pthread_self());
sleep(10);
return NULL;
}
void sys_err(const char*str)
{
perror(str);
exit(1);
}
int main()
{
int i=1;
int ret;
pthread_t tid;
ret = pthread_create(&tid,NULL,fun,(void*)i);
if(ret!=0)
{
sys_err("pthread_creat error");
}
ret=pthread_detach(tid);
if(ret==0)
{
printf("success pthread_detach\n");
}
ret = pthread_join(tid,NULL);
if(ret!=0)
{
printf("pthread_join error\n");
}
pthread_exit(NULL);
}

2.5.6 pthread_cancel 函式
作用:
殺死(取消)執行緒 其作用,對應行程中 kill() 函式,
int pthread_cancel(pthread_t thread);成功:0;失敗:錯誤號
執行緒的取消并不是實時的,而有一定的延時,需要等待執行緒到達某個取消點(檢查點),
取消點:是執行緒檢查是否被取消,并按請求進行動作的一個位置,通常是一些系統呼叫
creat,open,pause, close,read,write… 執行命令 man 7 pthreads
可以查看具備這些取消點的系統呼叫串列,也可參閱 APUE.12.7 取消選項小節,
可粗略認為一個系統呼叫(進入內核)即為一個取消點,如執行緒中沒有取消點,可以通過呼叫pthread_testcancel函式自行設定一個取消點,
2.6 執行緒行程控制原語比對
| 行程 | 執行緒 |
|---|---|
| fork | pthread_create |
| exit | pthread_exit |
| wait | pthread_join |
| kill | pthread_cancel |
| getpid | pthread_self |
2.7 執行緒屬性注意事項(重)
- 主執行緒退出其他執行緒不退出,主執行緒應呼叫 pthread_exit
- 避免僵尸執行緒
pthread_join
pthread_detach
pthread_create 指定分離屬性
被 join 執行緒可能在 join 函式回傳前就釋放完自己的所有記憶體資源,所以不應當回傳被回收執行緒堆疊中的值; - malloc 和 mmap 申請的記憶體可以被其他執行緒釋放
- 應避免在多執行緒模型中呼叫 fork 除非,馬上 exec,子行程中只有呼叫 fork 的執行緒存在,其他執行緒在子行程
中均 pthread_exit - 信號的復雜語意很難和多執行緒共存,應避免在多執行緒引入信號機制
如有錯誤歡迎指出…

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/277077.html
標籤:其他

