Linux學習之多執行緒程式設計(二)
言之者無罪,聞之者足以戒。 ——《詩序》
(二)、執行緒的基本控制
1、終止程序:
如果程序中的任意一個程序呼叫了exit、_exit、_Exit,那麼整個程序就會終止
普通的單個程序有以下3種退出方式,這樣不會終止程序:
(1)從啟動例程中返回,返回值是執行緒的退出碼
(2)執行緒可以被同一個程序中的其他程序取消
(3)執行緒呼叫pthread_exit(void *rval)函式,rval是退出碼
下面我們寫一個程式用一下三種返回方式:
#include <pthread.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <errno.h> void *thread_fun(void *arg) { if(strcmp("1",(char *)arg)==0) { printf("new thread return \n"); return (void *)1; } if(strcmp("2",(char *)arg)==0) { printf("new thread pthread_exit \n"); pthread_exit((void *)2); } if(strcmp("3",(char *)arg)==0) { printf("new thread exit \n"); exit(3); } } int main(int argc,char *argv[]) { int err; pthread_t tid; err=pthread_create(&tid,NULL,thread_fun,(void *)argv[1]); if(err!=0) { printf("create new thread failure\n"); return 0; } sleep(1); printf("main thread\n"); return 0; }
2、執行緒連線:
(1)pthread_join執行緒連線函式
int pthread_join(pthread_t tid,void **rval)
第一個引數:tid 就是指定執行緒的id
第二個引數:rval就是指定執行緒的返回碼,如果執行緒被取消,那麼rval被置為PTHREAD_CANCELED
返回值:呼叫成功返回0,失敗返回錯誤碼
呼叫該函式的執行緒會一直阻塞,直到指定的執行緒tid呼叫pthread_exit,從啟動例程返回或者被取消;呼叫該函式會使指定的執行緒處於分離狀態,如果指定的執行緒已經處於分離狀態,那麼呼叫就會失敗。
(2)pthread_detach分離執行緒函式
int pthread_detach(pthread_t thread)
引數:執行緒的id
返回值:成功返回0,失敗返回錯誤碼
呼叫該函式可以分離一個執行緒,執行緒也可以自己分離自己。
下面來看一下程式:
#include <pthread.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <errno.h> void *thread_fun1(void *arg) { printf("I am thread 1\n"); return (void *)1; } void *thread_fun2(void *arg) { printf("I am thread 2\n"); pthread_detach(pthread_self()); pthread_exit((void *)2); } int main() { int err1,err2; pthread_t tid1,tid2; void *rval1,*rval2; err1=pthread_create(&tid1,NULL,thread_fun1,NULL); err2=pthread_create(&tid2,NULL,thread_fun2,NULL); if(err1 || err2) { printf("creatr new thread failure\n"); return -1; } printf("I am is main thread\n"); printf("jion1 rval is %d\n",pthread_join(tid1,&rval1)); printf("jion2 rval is %d\n",pthread_join(tid2,&rval2)); printf("thread 1 exit code is%d\n",(int *)rval1); printf("thread 2 exit code is%d\n",(int *)rval2); printf("I am main thread\n"); return 0; }
3、執行緒的取消:
(1)pthread_cancel取消執行緒函式
int pthread_cancel(pthread_t tid)
引數:需要取消的執行緒的id
返回值:成功返回0,失敗返回錯誤碼
取消tid指定的執行緒,所謂的取消只是傳送一個請求,並不意味著等待執行緒終止,而且傳送成功也不代表執行緒一定會終止。該函式需要被取消執行緒的配合,執行緒在很多時候會檢視自己是否有取消請求,如果有就主動退出,這個檢視是否有取消的地方稱為取消點。
(2)pthread_setcancelstate設定執行緒對取消訊號的反應
int pthread_setcancelstate(int state,int *oldstate)
第一個引數:有兩個值:PTHREAD_CAMCEL_ENABLE(響應取消訊號)和PTHREAD_CANCEL_DISABLE(忽略取消訊號)
第二個引數:如果不為NULL則是儲存原來的取消狀態以便恢復
返回值:成功返回0 ,失敗返回錯誤碼
取消狀態,就是執行緒對取消訊號的處理方式,忽略或者響應。執行緒建立時預設的是響應取消訊號。
(3)pthread_setcanceltype設定執行緒取消動機的執行時機
int pthread_setcanceltype(int type, int *oldtype)
第一個引數:有兩個值:PTHREAD_CANCEL_DEFERRED(收到取消訊號後繼續執行至下一個取消點再退出)和PTHREAD_CANCEL_ASYNCHRONOUS(收到取消訊號後立即退出)。注意:這兩個引數是線上程響應取消訊號的時候才有作用(PTHREAD_CAMCEL_ENABLE)
第二個引數:oldtype如果不設定為NULL則儲存運來的取消動作型別值。
返回值:成功返回0,失敗返回錯誤碼
取消型別,是執行緒對取消訊號的響應方式,立即取消或者延時取消。執行緒建立時預設是延時取消。
下面先看一下程式的框架:
程式程式碼:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
void *thread_fun(void *arg)
{
int stateval;
int typeval;
stateval=pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
if(stateval != 0)
{
printf("set cancel state failure\n");
}
printf("I am xiaoyi\n");
sleep(4);
printf("about to cancel\n");
stateval=pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
if(stateval != 0)
{
printf("set cancel state failure\n");
}
typeval=pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
if(typeval != 0)
{
printf("set cancel type failure\n");
}
printf("first cancel point\n");
printf("second cancel point\n");
return (void *)10;
}
int main()
{
pthread_t tid;
int err,cval,jval;
void *rval;
err=pthread_create(&tid,NULL,thread_fun,NULL);
if(err != 0)
{
printf("create thread failure\n");
return -1;
}
sleep(2);
cval=pthread_cancel(tid);
if(cval != 0)
{
printf("cancel thread failure\n");
}
jval=pthread_join(tid,&rval);
printf("new thread exit code is %d\n",(int *)rval);
return 0;
}
4、向執行緒傳送訊號:
(1)pthread_kill傳送訊號函式
int pthread_kill(pthread_t thread,int sig)
第一個引數:執行緒的id
第二個引數:要傳送的訊號
返回值:成功返回0,失敗返回錯誤碼
作用:向指定ID的執行緒傳送sig訊號,如果執行緒程式碼內不做處理,則按照訊號預設的行為影響整個程序,也就是說,如果你給一個執行緒傳送了SIGQUIT訊號,但執行緒卻沒有實現signal處理函式,則整個程序會退出
如果第二個引數不是0 ,那就一定要清楚到底要幹什麼,而且一定要實現執行緒的訊號處理,否則就會影響整個程序;如果第二個引數是0 ,這就是一個保留訊號,其實並沒有傳送訊號,作用是用來判斷執行緒是不是還活著。
下面給出一段程式碼熟悉一下知識:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
void *thread_fun(void *arg)
{
// sleep(1);
printf("I am xiaoyi\n");
return (void *)0;
}
int main()
{
pthread_t tid;
int err;
int s;
void *rval;
err=pthread_create(&tid,NULL,thread_fun,NULL);
if(err != 0)
{
printf("create new thread failure\n");
return -1;
}
sleep(1);
s=pthread_kill(tid,0);
// s=pthread_kill(tid,SIGQUIT);
// if(s == ESRCH)
// {
// printf("thread tid is not found\n");
// }
if(s != 0)
{
printf("thread tid is not found\n");
}
// pthread_join(tid,&rval);
printf("I am main thread\n");
return 0;
}
(2)sigaction設定一個訊號的處理函式
int sigaction(int signum, const struct sigaction *act , struct sigaction *oldact)
第一個引數:訊號的名字
第二個引數:傳入新的處理方式
第三個引數:傳出舊的處理方式
返回值:成功返回0,失敗返回錯誤碼
struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); }
sa_handler:指定訊號捕捉後的處理函式名,也可以賦值為SIG_IGN表示忽略或SIG_DFT表示執行預設操作
sa_mask:呼叫訊號處理函式時,所要遮蔽的訊號集合(訊號遮蔽字)。注意:僅在處理函式被呼叫期間遮蔽生效,是臨時性設定。用sigaddset函式新增需要被捕捉的訊號。
sa_flags:通常設定為0,表示使用預設屬性,為0的時候,可以遮蔽正在處理的訊號(若在處理二號訊號的時候又有二號訊號,則此時傳來的二號訊號就會被遮蔽)
(3)sigemptyset清空訊號集
int sigemptyset(sigset_t *set)
引數:訊號集
返回值:成功返回0,失敗返回-1
(4)sigfillset將所有訊號加入訊號集
int sigfillset(sigset_t *set)
引數:訊號集
返回值:成功返回0,失敗返回-1
(5)sigaddset增加一個訊號到訊號集
int sigaddset(sigset_t *set , int signum)
第一個引數:訊號集
第二個引數:要增加的訊號
返回值:成功返回0,失敗返回-1
(6)sigdelset將指定訊號從訊號集中刪除
int sigdelset(sigset_t *set , int signum)
第一個引數:訊號集
第二個引數:要刪除的訊號
返回值:成功返回0,失敗返回-1
(7)pthread_sigmask在主執行緒中控制訊號掩碼
int pthread_sigmask(int show ,const sigset_t set, sigset_t *oldset)
第一個引數: SIG_BLOCK:向當前的訊號掩碼中新增set,其中set表示要阻塞的訊號組。
SIG_UNBLOCK:向當前的訊號掩碼中刪除set,其中set表示要取消阻塞的訊號組。
SIG_SETMASK:將當前的訊號掩碼替換為set,其中set表示新的訊號掩碼。
第二個引數:訊號組
第三個引數:以前的訊號組
返回值:成功返回0 ,失敗返回錯誤碼
在多執行緒中,新執行緒的當前訊號掩碼會繼承創造它的訊號掩碼。
一般情況下被阻塞的訊號將不能中斷此執行緒的執行,除非此訊號的產生是因為程式執行出錯;如SIGSEGV,另外不能被忽略處理的訊號 SIGKILL 和 SIGSTOP 也無法被阻塞。
我們寫一個程式學習一下上面的這些函式:
先看一下程式的框架:
再看一下程式的程式碼:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
void sig_handler1(int arg)
{
printf("thread1 get signal\n");
return;
}
void sig_handler2(int arg)
{
printf("thread2 get signal\n");
return;
}
void *thread_fun1(void *arg)
{
printf("new thread 1\n");
struct sigaction act;
memset(&act,0,sizeof(act));
sigaddset(&act.sa_mask,SIGQUIT);
act.sa_handler = sig_handler1;
sigaction(SIGQUIT,&act,NULL);
// pthread_sigmask(SIG_BLOCK,&act.sa_mask,NULL);
sleep(2);
}
void *thread_fun2(void *arg)
{
printf("new thread 2\n");
struct sigaction act;
memset(&act,0,sizeof(act));
sigaddset(&act.sa_mask,SIGQUIT);
act.sa_handler = sig_handler2;
sigaction(SIGQUIT,&act,NULL);
pthread_sigmask(SIG_BLOCK,&act.sa_mask,NULL);
sleep(2);
}
int main()
{
pthread_t tid1,tid2;
int err;
int s;
err = pthread_create(&tid1,NULL,thread_fun1,NULL);
if(err != 0)
{
printf("create new thread 1 failure\n");
return ;
}
err = pthread_create(&tid2,NULL,thread_fun2,NULL);
if(err != 0)
{
printf("create new thread 2 failure\n");
return ;
}
sleep(1);
s=pthread_kill(tid1,SIGQUIT);
if(s != 0)
{
printf("send signal to thread1 faailure\n");
}
s=pthread_kill(tid2,SIGQUIT);
if(s != 0)
{
printf("send signal to thread2 faailure\n");
}
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}
5、清除操作
執行緒可以安排它退出時的清理操作,這與程序的可以用atexit函式安排程序退出時需要呼叫的函式類似。這樣的函式稱為執行緒清理處理程式。執行緒可以建立多個清理處理程式,處理程式記錄在棧中,所以這些處理程式執行的順序與他們註冊的順序相反(先註冊的後執行)。
(1)pthread_cleanup_push註冊處理程式
void pthread_cleanup_push(void (*routine)(void *), void *arg)
第一個引數:一個處理函式
第二個的引數:處理函式的入口引數
返回值:無
(2)pthread_cleanup_pop清除處理程式
void pthread_cleanup_pop(int execute) 引數:非零即呼叫
返回值:無
當執行以下操作時呼叫清理函式,清理函式的引數由arg傳入:
1)呼叫pthread_exit 2)響應取消請求 3)用非零引數呼叫pthread_cleanup_pop
下面來看一下程式:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
void *first_clean(void *arg)
{
printf("%s first clean\n",arg);
return (void *)0;
}
void *second_clean(void *arg)
{
printf("%s second clean\n",arg);
return (void *)0;
}
void *thread_fun1(void *arg)
{
printf("new thread 1 \n");
pthread_cleanup_push(first_clean,"xiaoyi1");
pthread_cleanup_push(second_clean,"xiaoyi1");
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
return (void *)1;
}
void *thread_fun2(void *arg)
{
printf("new thread 2 \n");
pthread_cleanup_push(first_clean,"xiaoyi2");
pthread_cleanup_push(second_clean,"xiaoyi2");
pthread_cleanup_pop(0);
pthread_cleanup_pop(1);
pthread_exit((void *)2);
}
int main()
{
pthread_t tid1,tid2;
int err;
err =pthread_create(&tid1, NULL, thread_fun1, NULL);
if(err != 0)
{
printf("create new thread 1failed\n");
return;
}
err =pthread_create(&tid2, NULL, thread_fun2, NULL);
if(err != 0)
{
printf("create new thread 2failed\n");
return;
}
sleep(2);
return 0;
}
到這裡為止執行緒的基本操作就說完了。