- 庫函數中的初始化使用
pthread_once()
,PTHREAD_ONCE_INIT
linux
中once
分爲三個狀態NEVER(0)
、IN_PROGRESS(1)
、DONE(2)
- 若是once_control初值爲0,那麼pthread_once從未執行過,init_routine()函數會執行。
- 若是once_control初值設爲1,則因爲全部pthread_once()都必須等待其中一個激發"已執行一次"信號, 所以全部pthread_once ()都會陷入永久的等待中,init_routine()就沒法執行
- 若是once_control設爲2,則表示pthread_once()函數已執行過一次,從而全部pthread_once()都會當即 返回,init_routine()就沒有機會執行 當pthread_once函數成功返回,once_control就會被設置爲2。
#include <pthread.h>
pthread_once_t once = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t* once_control, void (*init_routine)(void));
複製代碼
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
pthread_once_t once = PTHREAD_ONCE_INIT;
pthread_t tid;
void thread_init() {
sleep(2);
printf("*********I'm in thread 0x%x\n", tid);
}
void *thread_fun2(void *arg) {
tid = pthread_self();
printf("I'm thread 0x%x\n", tid);
printf("once is %d\n", once);
pthread_once(&once, thread_init);
printf("once is %d\n", once);
return NULL;
}
void *thread_fun1(void *arg) {
sleep(1);
tid = pthread_self();
printf("I'm thread 0x%x\n", tid);
printf("*****once is %d\n", once);
pthread_once(&once, thread_init);
return NULL;
}
int main() {
pthread_t tid1, tid2;
int err;
err = pthread_create(&tid1, NULL, thread_fun1, NULL);
if(err != 0)
{
printf("create new thread 1 failed\n");
return ;
}
err = pthread_create(&tid2, NULL, thread_fun2, NULL);
if(err != 0)
{
printf("create new thread 1 failed\n");
return ;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
/ *
I'm thread 0x81bf1700
once is 0
I'm thread 0x823f2700
*****once is 1
*********I'm in thread 0x823f2700
once is 2
*/
複製代碼
- 避免了修改不可重入函數的參數
#include<pthread.h>
int pthread_key_create(pthread_key_t *key, void(*destructor)(void *));
// 解構函數destructor, 線程終止時將key的關聯值做爲參數轉給destructor
int pthread_setspecific(pthread_key_t key, const void * value);
// 通常value 是指向調用者分配的一塊內存,線程終止時會將value 傳給key對應的解構函數
int pthread_getspecific(pthread_key_t key);
// 返回當前線程綁定的value
複製代碼
key在當前進程的存貯linux
不一樣線程中的數據緩衝區,線程剛剛建立時會初始化爲null算法
通常流程shell
- 函數建立一個key,經過傳參調用
pthread_key_create()
的函數做爲pthread_once()
的參數,調用pthread_once()
保證一次建立key的行爲- 經過
pthread_setspecific()
和pthread_getspecific()
來綁定和確認key又沒用綁定線程獨立緩衝區,沒有的話malloc()分配一次,只會有一次。
static __thread buf[size];
複製代碼
__thread
需緊跟extern
或static
後面編程
#include<pthread.h>
int pthread_cancel(pthread_t thread);
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);
複製代碼
設置線程取消狀態和類型vim
- STATE 能否取消
- PTHREAD_CANCEL_DISABLE 掛起請求直到,取消狀態啓用
- PTHREAD_CANCEL_ENABLE
- TYPE
- PTHREAD_CANCEL_ASYNCHRONOUS, 可能會在任什麼時候點取消
- PTHREAD_CANCEL_DEFERED 取消請求掛起直到取消點
取消點緩存
- SUSv3規定了一些必須有取消點的函數
- 線程一點接受了取消信息,啓用取消性狀態而且類型置位延遲,其會在下次到達取消點終止,若沒有detach,爲了防止成爲殭屍線程,必須由其餘線程對其進行連接,鏈接以後
pthread_join()
中的第二個參數將會是:PTHREAD_CANCELD
# include<pthread.h>
void pthread_testcancel(void);
//線程的代碼中沒有取消點時,能夠經過調用其做爲取消點
複製代碼
清理函數安全
# include<pthread.h>
void pthread_cleanup_push(void (*routine)(void*), void *arg);
void pthread_cleanup_pop(int execute);
複製代碼
異步取消bash
- 可異步取消線程不該該分配任何資源,也不能獲取互斥量和鎖
- 可以使用在取消計算密集型的循環的線程
線程棧多線程
- 線程棧的大小能夠經過建立線程時的
pthread_attr_t
類型參數設定,線程棧越大,受制於用戶模式虛擬內存,並行的線程越少
線程和信號架構
- 信號動做屬於進程層面。ex:若是進程的某一線程接收到任何未經特殊處理的信號,其缺省動做爲STOP或TERMINATE,將終止該進程的全部線程
- 對信號的處置屬於進程層面。ex:IGNORE
- 信號的發送既可針對整個進程,也可針對某個特定線程
- 面向線程的狀況:
- 信號的產生源於線程上下文中對於特定硬件指令的執行(硬件異常:SIGBUG,SIGFPG,SIGILL,SIGSEGV)
- 線程試圖對已斷開的管道進行寫操做產生的SIGPIPE信號
- 由
thread_kill()
或者pthread_sigqueue()
發出的信號- 多線程程序收到一個信號時,有對應處理程序時,內核會選一個線程處理該信號
- 信號掩碼針對每一個線程
- 針對整個進程掛起的信號,和每條線程掛起的信號,內核有維持記錄,
sigpending()
會返回整個進程和當前線程掛起信號的並集。新線程,現成的掛起信號初始值爲空- 信號中斷了
thread_mutex_lock()``thread_mutex_wait()
的調用,則起調用會從新開始- 備選信號棧是線程獨有
#include<signal.h>
int pthread_sigmask(int how, const sigset_t *set, sigset_t *old_set);
//除了操做的是線程掩碼,其餘和sigprocmask()一致,多線程調用後者可能致使未定義問題
int pthread_kill(pthread_t thread, int sig);
//本進程線程發送信號
int pthread_sigqueue(pthread_t thread, int sig, const union sigval value);
//進程發送實時信號
複製代碼
異步信號的處理:阻塞全部線程,專有線程去處理
- 線程和
exec()
,調用程序將被替換,除了調用線程以外,其餘線程會當即消失,不會對線程特有數據額結構進行析構,也不會調用清理函數- 線程和
fork()
,只會講調用線程fork()
到子進程中,其餘線程馬上消失
- 會致使其餘線程的鎖未釋放,子進程的線程阻塞
- 不會對線程特有數據額結構進行析構,也不會調用清理函數,致使子進程內存泄漏
- 建議多線程調用
fork()
以後馬上調用exev()
- 線程和
exit()
如何線程執行了exit()
或主線程執行了return,全部線程消失,不會對線程特有數據額結構進行析構,也不會調用清理函數
- 多對一:線程建立的,調度,同步的全部細節由進程內用戶空間的線程庫處理(相似攜程?)
- 速度快,無需內核態切換
- 移植相對方便
- 當一個線程發起內核調用阻塞時,全部線程阻塞
- 內核感知不到縣城,沒法調度給不一樣的CPU,沒法調整線程優先級
- 一對一
- 避免了多對一弊端
- 可是維護每個KSE須要開銷,增長內核調度器的負擔
- 進程建立切換等操做比多對一慢
- 多對多
- 每一個進程擁有多個KSE, 而且能夠把多個線程映射到一個KSE,權衡商量中模型
- 可是模型過於複雜,調度由線程庫和內核共同實現
Linux POSIX的實現
- LinuxThread(舊)
- NPTL(一對一)
- 進程組是一組相關進程的集合
- 會話是一組相關進程組的集合
進程組
- 一個進程組擁有一個進程組的首進程,該進程是建立這個進程組的進程,其進程ID是進程組的ID
- 開始首進程建立組,結束語最後一個成員進程退出組
- 進程組首進程無需最後退出
- 新進程繼承父進程的進程組ID
- 特有屬性
- 特定進程組中父進程可以等待任意子進程26.1.2
- 信號能發給進程組中全部成員20.5
會話
- 會話首進程是建立新會話的進程, 其進程ID成爲會話ID
- 新進程會繼承父進程的會話ID
- 在任意時刻,會話中的其中一個進程組會成爲終端的前臺進程組,其餘爲後臺進程組
- 當到控制終端的鏈接創建起來以後,會話首進程會成爲該終端的控制進程
- 從shell中發出的某個命令或者經過管道鏈接的一組命令或致使一個或多個進程建立,並被放到一個新的進程組中
![]()
#include<unistd.h>
pid_t getgrp(void);
int setpgid(pid_t pid, pid_t pgid);
//如下等價將調用進程的進程組ID設爲調用進程的PID
setpgid(0, 0);
setpgid(getpid(), 0);
setpgid(getpid(), getpid());
複製代碼
pid 參數只能指定調用進程或其子進程 調用進程,pid指定進程,以及目標進程組需屬於同一會話 pid不能指定會話首進程 一個進程在其子進程執行過
exec()
後沒法修改子進程的進程組ID
# define _XOPEN_SOURCE 500
# include<unistd.h>
pid_t getsid(pid_t pid);
// pid爲0返回調用進程的會話ID
pid_t setsid(void);
// 調用進程不能爲進程組的首進程
複製代碼
- 調用進程會成爲新會話的首進程和該會話中新進程組的首進程
- 調用進程沒有控制終端,全部以前到控制終端的鏈接都會斷開
- 調用進程不能爲進程組的首進程,若是能夠的話,其進程組的原其餘進程的進程組ID會被動成爲另外一個會話的進程ID,破壞了會話和進程組之間嚴格的兩級層次,進程組的全部成員必須屬於同一會話
fork()建立一個新進程時,內核會確保其PID不會和已有進程的進程組ID和會話ID相同
$ ps -p $$ -o 'pid pgid sid command'
PID PGID SID COMMAND
10217 10217 10217 -bash
# $$ 是shell的PID
$ ./sid
PID=11762, PGID=11762, SID=11762
error in open # setsid以後進程再也不擁有控制終端
複製代碼
#define _XOPEN_SOURCE 500
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
int main (int argc, char *argv[]){
if (fork()!=0){
_exit(0);
}
if (setsid()==-1){
printf("error in setsid");
}
printf("PID=%ld, PGID=%ld, SID=%ld\n", (long) getpid(), (long) getpgrp(), (long) getsid(0));
if (open("/dev/tty", O_RDWR)==-1){
printf("error in open");
}
return 0;
}
複製代碼
- 一個會話中的全部進程可能會擁有一個控制終端
- 會話首進程首次打開一個還沒成爲某個會話控制終端的終端時會創建其控制終端,同時首進程成爲控制進程
- 控制終端斷開後,內核會想控制進程發送一個
SIGHUP
信號- 除非在調用
open(
)時指定O_NOCTTY
, 不然一箇中端只能成爲一個會話的控制終端- 控制終端會在
fork()
時集成,在exec()
時保持
ioctl(fd, TIOCNOTTY)
會刪除進程與文件描述符df指定終端的聯繫,若是調用進程是終端的控制進程
- 會話中全部進程失去與控制終端的聯繫
- 內核會向前臺進程組的全部成員發送一個SIGHUP信號(和一個SIGCONT信號)通知控制終端的丟失
- SIGHUP信號 默認行爲是終止進程,若忽略此信號,進程後續從終端讀取數據的請求會拋出異常
- 出如今:
- 終端驅動器檢測到鏈接斷開
- 終端窗口被關閉:(終端窗口關聯的僞終端的主測文件描述符被關閉了)
shell中處理
SIGHUP
信號(關聯行爲)
nohup
命令能夠用來使一個命令對SIGHUP的處置置位SIG_IGNdisown
(bash)會從shell的任務列表刪除一個任務,這樣其在shell終止時不會收到SIGHUP- shell一般會是終端的控制進程
- shell收到SIGHUP只會發SIGHUP給由它建立的進程組的進程(前臺和後臺進程)(若是子進程新建了個進程組不會被通知)
exec
會致使shell執行一個exec()
使指定程序替代本身 與shell不一樣,若是由於終端斷開引發的向控制進程發送的SIGHUB信號會致使控制進程終止,那麼SIGHUB會發送給終端的前臺進程組全部成員
fg %1
bg %1
複製代碼
%%
,%+
指當前做業%-
指上一次做業
- 後臺進程嘗試從終端讀會受到SIGTTIN信號,中止做業
- 終端設置了TOSTOP命令,後臺進程嘗試向終端輸出時,會收到SIGTTOU信號,中止做業
vim 這個程序在SIGSTP和SIGCONT須要額外的操做保持終端屏幕內容
若是一個進程組變成了孤兒進程組中幷包含許多被中止進程,SUSv3規定,系統會像進程組中全部成員發送SIGHUB,通知他們和會話斷開,再發送SIGCONT確保他們恢復執行
linux進程調度使用CPU默認是循環時間共享,每一個進程輪流使用CPU,這段時間被稱爲時間片
- 公平性:每一個進程都有機會使用CPU
- 響應度:一個進程在使用CPU以前無需等待太長時間
- 若是進程沒有sleep或者被I/O阻塞,他們使用CPU的時間是差很少的
進程特性的nice值容許,進程間接的影響內核的調度算法,取值範圍是-20(最高)~19(最低),默認0
- 只有特權進程才能賦值給本身或者其餘進程一個負的優先級
- 非特權進程只能下降本身的優先級,即賦值一個大於0的nice值
- fork()出建立的子進程會繼承nice值,並在exec時保留
- 進程的調度不是嚴格按照nice值得層次進行的,相反Nice值是一個權重因素
# include<sys/resource.h>
int getpriority(int which, id_t who);
//成功時返回nice值(-20~19),失敗時返回-1(和成功值重複)
int setpriority(int which, id_t who, int prio);
//成功0,失敗-1
複製代碼
who的值取決於which的值
- which = PRIO_PROCESS:操做進程ID爲who的進程,who爲0時,使用調用者的進程ID
- which = PRIO_PGRP:操做進程組ID爲who的進程組中的全部進程,若是who爲0,那麼使用調用者的進程組
- which = PRIO_USER:操做真實用戶ID爲who的進程,若是who爲0,使用調用者的真實用戶ID(不一樣unix實現,非特權進程對於真實用戶和有效用戶的匹配時能夠設置權限有區別)
getprioroty
當是多個進程是返回優先級最高的進程的nice值(由於可能返回-1,須要調用前將errno置0)
linux 內核2.6.12開始:
- linux提供了RLIMIT_NICE資源限制,容許非特權進程提高nice值,非特權進程能夠將本身提高到
20 - rlim_cur
的值- 非特權進程能夠經過
setpriority
來修改其餘目標進程的nice值,前提是調用setpriority()
的進程的有效用戶ID與目標進程的真實或有效用戶ID匹配,而且符合RLIMIT_NICE
限制
實時應用對調度器有更加嚴格的要求
- 實時應用必需要爲外部輸入提供擔保最大響應時間
- 高優先級進程可以保持互斥的訪問CPU直到他完成或自動釋放CPU
- 實時應用進程可以精確地控制其組建進程的調度順序
- SUSv3實時進程調用API提供的策略(同時用SCHED_OTHER標記循環時間分享策略,如下優先級均高於其):
- SCHED_RR
- SCHED_FIFO
- linux 提供了99(1(低)~99(高))個實時優先級,以上兩個策略中的優先級是等價的
- 每一個優先級維護者一個可運行隊列
- POSIX實時(軟實時)與硬實時。和時間分享應用程序有衝突;linux2.6.18以後爲硬實時應用程序提供了徹底的支持
- SCHED_RR(循環)策略:優先級相同的進程以循環時間分享的方式執行,每次使用CPU的時間爲一個固定長度的時間片,一旦被調度執行以後會保持對CPU的控制直到:
- 達到時間片的終點
- 自願放棄CPU,多是執行了
sched_yield()
- 終止了
- 被更高優先級的進程搶佔了
- 以前被阻塞的高優先級進程解除阻塞了
- 別的進程優先級提升或本身優先級下降
- 前二者會將進程置於其優先級隊列隊尾,最後一個在搶佔進程結束後執行剩餘時間片
- 不一樣於SCHED_OTHER,SCHED_RR是嚴格按照優先級來的
- SCHED_FIFO:不一樣於SCHED_RR,SCHED_FIFO不存在時間片,被調度執行以後會保持對CPU的控制直到:
- 自願放棄CPU,多是執行了
sched_yield()
- 終止了
- 被更高優先級的進程搶佔了(和SCHED_RR情形同樣)
- 第一種狀況會將進程置於其優先級隊列隊尾,最後一個優先級進程結束(終止或者被阻塞)以後,被搶佔進程繼續執行
- SCHED_BATCH,SCHED_IDLE 略(非標準)
#include<sched.h>
int sched_get_priority_min(int policy);
int sched_get_priority_max(int policy);
// 不一樣操做系統min,max值不一樣,不必定是1~99
int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);
struct sched_param{
int sched_priority;
}
// 對於linux, SCHED_RR和SCHED_FIFO, sched_priority值必須在min和max之間,其它策略值只能是0
// 成功調用以後會將進程置於隊尾
// fork()建立的子進程會繼承父進程的調度策略和優先級,並在exec()中保持
int sched_setparam(pid_t pid, const struct sched_param *param);
複製代碼
從linux2.6.12開始,引入RLIMIT_RTPRIO,使非特權進程按照一點規則修改CPU調度
- 進程擁有非0
RLIMIT_RTPRIO
軟限制時,能夠任意修改本身的調度策略和優先級,優先級上限爲當前實時優先級的最大值及其RLIMIT_RTPRIO
軟限制的約束- 若是
RLIMIT_RTPRIO
爲0,進程只能下降優先級或者從實時策略轉化爲非實時策略- SCHED_IDLE 策略是特殊的策略,此時進程沒法修改本身策略
- 在其餘非特權進程也能執行策略和優先級的修改,只有有效用戶ID是目標進程的真實或有效用戶ID
- 防止實時進程鎖住系統:略
- 避免子進程進程特權調度策略(避免fork繼承):
- 當調用
sched_setscheduler()
時policy傳SCHED_RESET_ON_FORK時,由這個進程建立的子進程不會繼承特權進程的調度策略和優先級
- 若是調用進程策略是SCHED_RR或SCHED_FIFO,則紫禁城策略會被置爲SCHED_OTHER
- 若是nice<0,則置爲0
#include<sched.h>
int sched_yield(void);
//在非實時進程調用的結果是未定義的
int sched_rr_get_interval(pid_t pid, struct timespec *tp);
//獲取RR策略下的每次被受權CPU時間長度
複製代碼
- 進程切換CPU(原來的CPU處於忙碌狀態)
- 若是原來CPU的高速緩存保存進程數據,爲了將進程的這一行數據加載到新的CPU,首先須要使這行數據失效(沒被修改時丟棄,修改時寫入內存),(爲防止高速緩衝不一致,多處理器架構某一時刻只容許數據被存放在一個CPU的高速緩衝中)
- 爲減小以上的性能損耗,Linux2.6以後加入了CPU親和力
- 親和力的設置同調度策略
#define _GNU_SOURCE
#include<sched.h>
int sched_setaffinity(pid_t pid, size_t len, cpu_set_t *set);
複製代碼