1. 程式人生 > >第12章——《執行緒控制》(2)

第12章——《執行緒控制》(2)

實驗環境介紹

  • 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
其他執行緒相關的函式
// 將呼叫執行緒的可取消狀態設定為狀態中給定的值。以前執行緒的可取消狀態在oldstate指向的緩衝區中返回;
// PTHREAD_CANCEL_ENABLE:執行緒是可取消的。這是所有新執行緒(包括初始執行緒)中的預設可取消狀態。執行緒的可取消性型別決定了可取消執行緒何時對cancel做出響應
// PTHREAD_CANCEL_DISABLE:執行緒不可取消。如果接收到取消請求,它將被阻塞,直到啟用可取消性。
int pthread_setcancelstate(int state, int *oldstate); // 將呼叫執行緒的可取消型別設定為型別中給定的值。執行緒的前一個可取消型別返回到由oldtype指向的緩衝區中。 // PTHREAD_CANCEL_DEFERRED:取消點為下一個被呼叫的函式(即執行緒收到cancel通知後,執行緒下一次呼叫函式的時候才會被cancel)。這是所有新執行緒(包括初始執行緒)中的預設可取消型別。 // PTHREAD_CANCEL_ASYNCHRONOUS:執行緒可以在任何時候被取消。(通常,它會在收到取消請求後立即取消,但系統不能保證這一點。)
int pthread_setcanceltype(int type, int *oldtype); // pthread_once()函式不是一個取消點。但是,如果init_routine是一個取消點並被取消,那麼對once_control的影響應該是pthread_once()從未被呼叫。 int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)); pthread_once_t once_control = PTHREAD_ONCE_INIT; // 分別使用pthread_mutexattr_getprotocol()和pthread_mutexattr_setprotocol()函式來獲取和設定由attr指向的互斥物件的協議屬性,該屬性之前是由函式pthread_mutexattr_init()建立的。(和優先順序相關)
// 協議屬性定義了在使用互斥物件時要遵循的協議。協議的值可以是: // PTHREAD_PRIO_NONE: 當一個執行緒擁有一個帶有PTHREAD_PRIO_NONE協議屬性的互斥物件時,它的優先順序和排程不會受到互斥物件所有權的影響。 // PTHREAD_PRIO_INHERIT: 當一個執行緒因為擁有一個或多個具有PTHREAD_PRIO_INHERIT協議屬性的互斥鎖而阻塞較高優先順序的執行緒時,它應該以較高的優先順序或最高優先順序執行緒的優先順序執行,並等待該執行緒所擁有的且用該協議初始化的任何一個互斥鎖 // PTHREAD_PRIO_PROTECT:當一個執行緒擁有一個或多個用PTHREAD_PRIO_PROTECT協議初始化的互斥物件時,它應該以其優先順序較高或該執行緒(擁有的所有互斥物件並使用此屬性初始化)優先順序上限中最高的優先順序執行,而不管其他執行緒是否在這些互斥鎖上被阻塞。 // 當一個執行緒持有互斥鎖已初始化PTHREAD_PRIO_INHERIT或PTHREAD_PRIO_PROTECT協議屬性,它不受被搬到尾排程佇列的優先順序時,最初的重點是改變,如通過呼叫sched_setparam ()。同樣,當一個執行緒解鎖一個用PTHREAD_PRIO_INHERIT或PTHREAD_PRIO_PROTECT協議屬性初始化的互斥物件時,當它的初始優先順序發生變化時,它不會被移動到排程佇列的尾部。如果一個執行緒同時擁有多個用不同協議初始化的互斥物件,那麼它應該以每個協議可能獲得的最高優先順序執行。當一個執行緒呼叫pthread_mutex_lock(),互斥鎖是初始化協議PTHREAD_PRIO_INHERIT屬性的值,呼叫執行緒阻塞時因為互斥鎖是由另一個執行緒持有,主人執行緒必承受呼叫執行緒的優先順序,只要它繼續自己的互斥鎖。執行應將其執行優先順序更新到其分配的優先順序和其繼承的所有優先順序的最大值。此外,如果這個所有者執行緒本身在另一個互斥鎖上被阻塞,同樣的優先順序繼承效應將以遞迴的方式傳播到另一個所有者執行緒。 int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *restrict attr, int *restrict protocol); int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol); // 返回互斥鎖的當前優先順序上限。 int pthread_mutex_getprioceiling(const pthread_mutex_t *restrict mutex, int *restrict prioceiling); // 如果互斥鎖被解鎖,函式要麼鎖定互斥鎖,要麼阻塞直到它成功鎖定互斥鎖,然後它將改變互斥鎖的優先順序上限並釋放互斥鎖。當更改成功後,優先順序上限的先前值將在old_ceiling中返回。鎖定互斥鎖的過程不需要遵循優先順序保護協議。 int pthread_mutex_setprioceiling(pthread_mutex_t *restrict mutex, int prioceiling, int *restrict old_ceiling); // 排程時執行緒搶佔資源範圍,系統範圍和程序內範圍 // PTHREAD_SCOPE_SYSTEM:該執行緒與系統中處於同一排程分配域(一組一個或多個處理器)的所有程序中的所有其他執行緒競爭資源。PTHREAD_SCOPE_SYSTEM執行緒是根據它們的排程策略和優先順序彼此排程的。 // PTHREAD_SCOPE_PROCESS:執行緒與同一程序中的所有其他執行緒競爭資源,這些執行緒也是用PTHREAD_SCOPE_PROCESS爭用作用域建立的。PTHREAD_SCOPE_PROCESS執行緒是根據其排程策略和優先順序相對於程序中的其他執行緒進行排程的。POSIX。在1-2001中,沒有指定這些執行緒如何與系統上其他程序中的其他執行緒競爭,或者與使用PTHREAD_SCOPE_SYSTEM爭用作用域建立的同一程序中的其他執行緒競爭。 int pthread_attr_setscope(pthread_attr_t *attr, int scope); int pthread_attr_getscope(pthread_attr_t *attr, int *scope); // 獲取、設定執行緒的排程策略和引數 pthread_setschedparam(pthread_t thread, int policy, const struct sched_param *param); pthread_getschedparam(pthread_t thread, int *policy, struct sched_param *param); // pthread_attr_setinheritsched()函式將attr引用的執行緒屬性物件的繼承性排程器屬性設定為inheritsched中指定的值。繼承性排程器屬性決定使用執行緒屬性物件attr建立的執行緒是否會從呼叫執行緒繼承其排程屬性,或者是否會從attr繼承它們。 int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched); int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);

重入

  • 如果一個函式對多個執行緒來說是可重入的,就說這個函式是執行緒安全的

執行緒私有資料

  • 可以防止某個執行緒資料和其他執行緒的資料相混淆
  • 提供了讓基於程序的介面適應多執行緒環境的機制:比如errno,被定位為執行緒私有資料
  • 除了使用暫存器外,一個執行緒沒有辦法阻止另外一個執行緒訪問他的資料
  • 使用pthread_key_create可以建立屬於執行緒自己的變數
#include <malloc.h>

#include <pthread.h>

#include <stdio.h>

/* The key used to associate a log file pointer with each thread. */

static pthread_key_t thread_log_key;

/* Write MESSAGE to the log file for the current thread. */

void write_to_thread_log (const char* message)

{
    FILE* thread_log = (FILE*) pthread_getspecific (thread_log_key);

    fprintf (thread_log, "%s\n", message);
}

/* Close the log file pointer THREAD_LOG. */

void close_thread_log (void* thread_log)

{
    fclose ((FILE*) thread_log);
}

void* thread_function (void* args)

{
    char thread_log_filename[20];
    FILE* thread_log;

    /* Generate the filename for this thread’s log file. */

    sprintf (thread_log_filename, "thread%d.log", (int) pthread_self ());

    /* Open the log file. */

    thread_log = fopen (thread_log_filename, "w");

    /* Store the file pointer in thread-specific data under thread_log_key. */

    pthread_setspecific (thread_log_key, thread_log);

    // 因為thread_log變數指向本執行緒擁有,但是某個函式也會使用這個變數,那個函式又會在這個執行緒中呼叫
    write_to_thread_log ("Thread starting.");

    /* Do work here... */

    return NULL;
}

int main ()
{
    int i;
    pthread_t threads[5];

    /* Create a key to associate thread log file pointers in

        thread-specific data. Use close_thread_log to clean up the file

        pointers. */

    pthread_key_create (&thread_log_key, close_thread_log);

    /* Create threads to do the work. */
    for (i = 0; i < 5; ++i)

        pthread_create (&(threads[i]), NULL, thread_function, NULL);

    /* Wait for all threads to finish. */
    for (i = 0; i < 5; ++i)

        pthread_join (threads[i], NULL);

    return 0;
}

result:
[[email protected] 新建資料夾]# ./main
[[email protected] 新建資料夾]# ls
email.py main main.c thread1212987136.log thread1221379840.log thread1229772544.log thread1238165248.log thread1246557952.log
[[email protected] 新建資料夾]# cat thread12*
Thread starting.
Thread starting.
Thread starting.
Thread starting.
Thread starting.

取消選項

  • 有兩個屬性沒有在pthread_attr_t中:
    • 可取消狀態:PTHREAD_CANCEL_ENABLE、PTHREAD_CANCEL_DISABLE
    • 可取消型別:PTHREADCANCEL_DEFERRED、PTHREAD_CANCEL_ASYNCHRONOUS

執行緒和訊號

  • 單個執行緒可以阻止某些訊號,當某個執行緒修改了與某個給定訊號相關的處理行為以後,所有執行緒都必須共享這個處理行為的改變
  • 在一個執行緒中呼叫signal或者sigaction等函式會改變所有執行緒中的訊號處理函式,而不是僅僅改變呼叫signal/sigaction的那個執行緒的訊號處理函式。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

#define handle_error_en(en, msg) \
    do {errno = en; perror(msg); exit(EXIT_FAILURE); }while(0)

void sig_func(int signo)
{
    printf("%lld get %d\n", pthread_self(), signo);
}

static void *sig_thread1(void *arg)
{
    sigset_t *set = (sigset_t *)arg;
    int ret, sig;

    // sigaddset(set, SIGUSR1);
    // pthread_sigmask(SIG_SETMASK, set, NULL);
    signal(SIGUSR1, sig_func);
    for (;;)
    {
        // 等待被遮蔽的訊號 SIGQUIT
        printf("before sigwait1..\n");
        ret = sigwait(set, &sig);
        if (ret != 0)
            handle_error_en(ret, "thread2 sigwait");

        printf("Signal handling thread1 got signal %d\n", sig);
    }
}

static void *sig_thread2(void *arg)
{
    sigset_t set;
    sigemptyset(&set);

    int ret, sig;
    sleep(2);
    for (;;)
    {
        // thread2 沒有設定捕捉任何訊號,
        // 除SIGQUIT被遮蔽,其餘訊號均可導致執行緒退出
        printf("before sigwait2..\n");
        ret = sigwait(&set, &sig);
        if (ret != 0)
            handle_error_en(ret, "thread2 sigwait");

        printf("Signal handling thread2 got signal %d\n", sig);
    }
}
int main(int argc, const char *argv[])
{
    pthread_t thread1;
    pthread_t thread2;
    sigset_t set;
    int ret;

    sigemptyset(&set);
    sigaddset(&set, SIGQUIT);

    // SIG_BLOCK 遮蔽 SIG_UNBLOCK 解遮蔽 SIG_SETMASK 設定
    ret = pthread_sigmask(SIG_BLOCK, &set, NULL);
    if (ret!=0 )
        handle_error_en(ret, "pthread_sigmask");

    ret = pthread_create(&thread1, NULL, sig_thread1, &set);
    ret = pthread_create(&thread2, NULL, sig_thread2, NULL);

    sleep(2);
    pthread_kill(thread1, SIGUSR1);
    printf("kill thread1 SIGUSR1.\n");

    sleep(2);
    pthread_kill(thread2, SIGUSR1);
    printf("kill thread2 SIGUSR1.\n");

    pause();

    return 0;
}

result:
[[email protected] 新建資料夾]# ./main
before sigwait1..
kill thread1 SIGUSR1.
139684937352960 get 10
before sigwait2..
kill thread2 SIGUSR1.
139684928960256 get 10
  • 如果某執行緒收到SIGUSR1時,預設處理是程序終止
Program received signal SIGUSR1, User defined signal 1.

程式就退出了。看我還想繼續執行呢。解決方法如下:

run以前設定程式收到SIGUSR1訊號時,不會退出就可以了。

(gdb) handle SIGUSR1 nostop
Signal Stop Print Pass to program Description
SIGUSR1 No Yes Yes User defined signal 1
(gdb) R

執行緒和fork

  • 執行緒呼叫fork時,會為子程序建立整個進城地址空間的副本。繼承了父程序每個互斥量、讀寫鎖和條件變數的狀態。如果子程序不是馬上呼叫exec的話,就需要清理鎖狀態
  • posix.1宣告,在fork返回和子程序呼叫其中一個exec函式之間,只能呼叫非同步訊號安全的函式。

執行緒和io

  • pread和pwrite使讀寫操作和偏移(lseek)成為了一個原子操作