1. 程式人生 > >第11章——《執行緒》(1)

第11章——《執行緒》(1)

實驗環境介紹

  • gcc:4.8.5
  • glibc:glibc-2.17-222.el7.x86_64
  • os:Centos7.4
  • kernel:3.10.0-693.21.1.el7.x86_64

執行緒概念

  • 忽略(ps:Linux是用程序實現的執行緒)
  • 程序是資源分配的基本單位,執行緒是排程的基本單位。

執行緒標識

  • 忽略

執行緒建立

  • 執行緒建立時不會保證哪個執行緒先執行
  • 新建立的執行緒會繼承呼叫執行緒的浮點環境和訊號遮蔽字,但是該新執行緒的掛起訊號集會被清楚,測試程式碼如下:
#include
<stdio.h>
#include <stdlib.h> #include <sys/wait.h> #include <stdarg.h> #include <signal.h> #include <pthread.h> #include <errno.h> #include <unistd.h> void sig_int(int signo); void *func(void *arg); void pr_mask(const char *str); int main(int argc, char
*argv[]) { if (signal(SIGINT, sig_int) == SIG_ERR) { printf("register signal INT error"); exit(EXIT_FAILURE); } // block INT sigset_t bset; sigemptyset(&bset); sigaddset(&bset, SIGINT); // 更新程序遮蔽訊號狀態字 if (sigprocmask(SIG_BLOCK, &bset, NULL) !=
0) { printf("sigprocmask() failed !\n"); return -1; } printf("%d: now sleep for 10 sec for waitint SIGINT\n", pthread_self()); sleep(30); // create a thread pthread_t thr1; if(pthread_create(&thr1,NULL,func, NULL)!=0) { printf("create thread failed!\n"); return -1; } printf("%d: now wake and create thread over. unblock SIGINT signal\n", pthread_self()); sigset_t set; if (sigaddset(&set, SIGINT) != 0) { printf("thread add set error\n"); } // 主執行緒這裡解除INT訊號的阻塞,這樣主線就會呼叫訊號處理函式 if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) { printf("thread sigprocmask() failed !\n"); return -1; } printf("%d: after unblocking SIGINT, main thread sleep\n", pthread_self()); while (1) sleep(1); return 0; } void sig_int(int signo) /* interrupts pause() */ { printf("%d: SIGINT received\n", pthread_self()); } void *func(void *arg) { pr_mask("before unblock:"); while (1) sleep(1); sigset_t set; if (sigaddset(&set, SIGINT) != 0) { printf("thread add set error\n"); } // 這裡會接觸INT訊號的阻塞,但是該執行緒被掛起的訊號集會被清楚 // 所以該執行緒不會呼叫訊號處理函式 if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) { printf("thread sigprocmask() failed !\n"); return (void *)-1; } printf("%d: child thread over\n", pthread_self()); while (1) sleep(1); return NULL; } void pr_mask( const char *str ){ sigset_t set; int errno_save; //get the pre errno errno_save = errno; if( sigprocmask( 0, NULL , &set ) == -1 ) printf("%d: sigprocmask error\n", pthread_self()); else { printf( "\n%s" , str ); if(sigismember(&set, SIGQUIT)) printf( " SIGQUIT" ); if(sigismember(&set, SIGINT)) printf( " SIGINT" ); if(sigismember(&set, SIGUSR1)) printf( " SIGUSR1" ); if( sigismember( &set , SIGALRM ) ) printf( " SIGALRM" ); } printf("\n"); errno = errno_save ; } // kill -2 8379 // kill -2 8379 // kill -2 8379 result: -1168320768: now sleep for 10 sec for waitint SIGINT -1168320768: now wake and create thread over. unblock SIGINT signal before unblock: SIGINT -1168320768: SIGINT received -1168320768: after unblocking SIGINT, main thread sleep
  • 控制終端的訊號傳送給該程序的主執行緒,測試程式碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>

void *func(void *arg);
void sig_int(int signo); /* interrupts pause() */

int
main(int argc, char *argv[])
{

    signal(SIGINT, sig_int);
    pthread_t thr1;
    printf("%lu: main thread\n", pthread_self());
    if(pthread_create(&thr1,NULL,func, NULL)!=0) {
        printf("create thread failed!\n");
        return -1;
    }

    while (1) sleep(1);
    return 0;
}

void
sig_int(int signo) /* interrupts pause() */
{
    printf("%lu: SIGINT received\n", pthread_self());
}

void *func(void *arg)
{

    while (1) sleep(1);
    return NULL;
}

result:
140463559870208: main thread
^C140463559870208: SIGINT received
^C140463559870208: SIGINT received
^C140463559870208: SIGINT received
^\Quit

執行緒終止

  • 執行緒終止的方式:
    • 如果任意執行緒呼叫exit、_Exit或者_exit,那麼整個程序就會終止
    • 如果預設的動作是終止程序,那麼,傳送到該執行緒的訊號會終止整個程序(12章再來討論訊號和執行緒)
    • 單個執行緒有三種退出方式
      • 執行緒可以從啟動例程中返回,返回值是執行緒的退出碼
      • 執行緒可以被同一程序中的其他執行緒取消
      • 執行緒呼叫pthread_exit來結束
  • 相關函式:pthread_exit、pthread_join
    1
    2
    • pthread_join可以獲取到pthread_exit的rval_ptr,如果執行緒是被取消的,則pthread_join的rval_ptr指向的記憶體單元為PTHREAD_CANCELED
    • pthread_join也自動把執行緒置於分離狀態
    • pthread_join可以回收同一個程序的其他執行緒

注意線上程函式中返回的指標是合法的,該指標的資料分配不該是線上程函式的棧上分配。這樣在別的執行緒中進行進行join的時候,該地址已經不合法

  • pthread_cancel函式
    • 該函式並不等待執行緒終止,它僅僅提出請求
    • pthread_cleanup_push函式可以註冊執行緒退出清理函式,這些清理函式被pthread_cleanup_push函式排程,pthread_cleanup_pop函式是刪除執行緒的清理函式
    • pthread_cleanup_push函式註冊的函式的呼叫時機為:
      • pthread_exit時
      • 被別的執行緒進行pthread_cancel時
      • pthread_cleanup_pop的引數非0時
    • 測試程式碼如下
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>

#define MAXLINE 2048

static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
    char buf[MAXLINE];

    vsnprintf(buf, MAXLINE-1, fmt, ap);
    if (errnoflag)
        snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
          strerror(error));
    strcat(buf, "\n");
    fflush(stdout); /* in case stdout and stderr are the same */
    fputs(buf, stderr);
    fflush(NULL); /* flushes all stdio output streams */
}


void
err_exit(int error, const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, error, fmt, ap);
    va_end(ap);
    exit(1);
}


void
cleanup(void *arg)
{
    printf("cleanup: %s\n", (char *)arg);
}

void *
thr_fn1(void *arg)
{
    printf("thread 1 start\n");
    pthread_cleanup_push(cleanup, "thread 1 first handler");
    pthread_cleanup_push(cleanup, "thread 1 second handler");
    printf("thread 1 push complete\n");
    if (arg)
        return((void *)1); // 這裡不會呼叫清理函式,在別的平臺上在這裡發會可能會產生未定義的行為,應該使用pthread_exit來返回,但是這樣就會執行清理函式
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
    return((void *)1);
}

void *
thr_fn2(void *arg)
{
    printf("thread 2 start\n");
    pthread_cleanup_push(cleanup, "thread 2 first handler");
    pthread_cleanup_push(cleanup, "thread 2 second handler");
    printf("thread 2 push complete\n");
    if (arg)
        pthread_exit((void *)2); // 這裡會觸發呼叫清理函式

    // 如果走到這裡,則清理函式不會呼叫
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
    pthread_exit((void *)2);
}

int
main(void)
{
    int err;
    pthread_t tid1, tid2;
    void *tret;

    err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
    if (err != 0)
        err_exit(err, "can't create thread 1");
    err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
    if (err != 0)
        err_exit(err, "can't create thread 2");
    err = pthread_join(tid1, &tret);
    if (err != 0)
        err_exit(err, "can't join with thread 1");
    printf("thread 1 exit code %ld\n", (long)tret);
    err = pthread_join(tid2, &tret);
    if (err != 0)
        err_exit(err, "can't join with thread 2");
    printf("thread 2 exit code %ld\n", (long)tret);
    exit(0);
}

result:
thread 1 start
thread 1 push complete
thread 2 start
thread 2 push complete
thread 1 exit code 1
cleanup: thread 2 second handler
cleanup: thread 2 first handler
thread 2 exit code 2
  • 程序和執行緒的原語比較:
    在這裡插入圖片描述