1. 程式人生 > >Linux學習之多執行緒程式設計(二)

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;

}

到這裡為止執行緒的基本操作就說完了。