1. 程式人生 > 其它 >Linux 系統程式設計 學習:10-執行緒:執行緒的屬性

Linux 系統程式設計 學習:10-執行緒:執行緒的屬性

背景

上一講我們介紹了執行緒的建立,回收與銷燬;簡單地提到了執行緒屬性。這一講我們就來具體看看,執行緒的屬性。

概述

c
#include<pthread.h>

typedef struct __pthread_attr_s
{
    int                       __detachstate;   // 執行緒的分離狀態
    int                       __schedpolicy;   // 執行緒排程策略
    structsched_param         __schedparam;    // 執行緒的排程引數
    int
__inheritsched; // 執行緒的繼承性 int __scope; // 執行緒的作用域 size_t __guardsize; // 執行緒棧末尾的警戒緩衝區大小 int __stackaddr_set; // 執行緒的棧設定 void* __stackaddr; // 執行緒棧的位置 size_t __stacksize; // 執行緒棧的大小
} pthread_attr_t; intpthread_attr_init(pthread_attr_t *attr); intpthread_attr_destroy(pthread_attr_t *attr);

執行緒具有屬性,用pthread_attr_t表示,在對該結構進行處理之前必須進行初始化(pthread_attr_init),在使用後需要對其去除初始化(pthread_attr_destroy)。

初始化為預設屬性

c
intpthread_attr_init(pthread_attr_t *attr);

描述:初始化一個執行緒屬性物件,重置為當前系統支援執行緒的所有屬性的預設值。

屬性
__scope PTHREAD_SCOPE_PROCESS
__tetachstate PTHREAD_CREATE_JOINABL
__stackaddr NULL
__stacksize 1M
__sched_param.priority 0
__inheritsched PTHREAD_INHERIT_SCHED
__schedpolicy SCHED_OTHER

反初始化

c
intpthread_attr_destroy(pthread_attr_t *attr);

描述:銷燬一個執行緒屬性物件,使它在重新初始化之前不能重新使用。

原理:用無效的值設定了屬性物件。

因此:如果經pthread_attr_destroy去除初始化之後的pthread_attr_t結構被pthread_create函式呼叫,將會導致其返回錯誤。

detachstate 分離狀態

我們來分析結構體中的有關成員。

c
intpthread_attr_setdetachstate(pthread_attr_t* attr, int detachstate);

// 有set就有get 。
intpthread_attr_getdetachstate(constpthread_attr_t* attr, int *detachstate)

描述:設定執行緒是否和其他執行緒分離(能否呼叫pthread_join()回收), 執行時可以呼叫pthread_detach()完成。

c
intpthread_detach(pthread_t thread);

引數解析:

attr:設定的屬性物件

detachstate :分離狀態

  • PTHREAD_CREATE_JOINABLE(預設):執行緒的資源在退出後自行釋放。
  • PTHREAD_CREATE_DETACHED:

設定為PTHREAD_CREATE_DETACH狀態(不論是建立時設定還是執行時設定) 則不能再恢復到PTHREAD_CREATE_JOINABLE狀態。

返回值:成功返回0,失敗返回錯誤號。

schedpolicy 排程策略與優先順序

排程策略

如果主執行緒是唯一的執行緒,那麼基本上不會被排程出去。另一方面,如果可執行的執行緒數大於CPU的數量,那麼作業系統最終會將某個正在執行的執行緒排程出去,從而使其他執行緒能夠使用CPU。這將導致一次上下文切換。在這個過程中將儲存當前執行執行緒的執行上下文,並將新排程進來的執行緒的執行上下文設定為當前上下文。

Linux核心的三種排程策略:

  • SCHED_OTHER 分時排程策略,預設的排程策略
  • SCHED_FIFO 實時排程策略,先到先服務。一旦佔用cpu則一直執行。一直執行直到有更高優先順序任務到達或自己放棄
  • SCHED_RR 實時排程策略,時間片輪轉。當程序的時間片用完,系統將重新分配時間片,並置於就緒佇列尾。放在佇列尾保證了所有具有相同優先順序的RR任務的排程公平

繼承建立者的排程策略

只有在不繼承時,下面的操作才是有效的。

c
intpthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
intpthread_attr_getinheritsched(constpthread_attr_t *attr, int *inheritsched);

描述:設定執行緒是否繼承建立者優先順序屬性

引數解析:

inheritsched : 是否繼承

  • PTHREAD_INHERIT_SCHED 繼承
  • PTHREAD_EXPLICIT_SCHED 不繼承

獲取可設定的優先順序

c
intsched_get_priority_max(int policy);
intsched_get_priority_min(int policy);

描述:獲取本執行緒的最大/小優先順序。

返回值:成功時返回最大/小值,失敗返回-1。

設定執行緒的排程策略與優先順序

c
intpthread_setschedparam(pthread_t thread, int policy,
                          const struct sched_param *param);
intpthread_getschedparam(pthread_t thread, int *policy,
                          struct sched_param *param);
/* 用到的結構體 */
structsched_param {
    int sched_priority;     /* Scheduling priority */
};

描述:設定執行緒的排程策略, 執行時可以呼叫pthread_setschedparam()來改變。

引數解析:

policy:

  • (預設)SCHED_OTHER(正常、非實時)
  • SCHED_RR(實時、輪轉法)
  • SCHED_FIFO(實時、先入先出)

param:優先順序(越大越高)

優先順序與排程的例程

c
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>

void *task0(void *arg){
    while(1)
    {
        //sleep(1);
        printf("task0.\n");
    }
}

void *task1(void *arg){
    while(1)
    {
        sleep(1);
        printf("task1.\n");
    }
}

intmain(void){
    pthread_attr_t attr;		
    structsched_paramparm;
    pthread_t tid0, tid1;
    void* retval;

    pthread_attr_init(&attr);

    pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); //不繼承建立者的排程策略,而是設定以下的排程
    pthread_attr_setschedpolicy(&attr, SCHED_FIFO); //為執行緒屬性設定排程策略

    parm.sched_priority = 1;					// 設定執行緒優先順序
    pthread_attr_setschedparam(&attr,&parm);	// 設定執行緒優先順序

    pthread_create(&tid0, &attr, task0, NULL);  // 為執行緒task0設定優先順序
    pthread_create(&tid1, NULL , task1, NULL);  // 讓執行緒task1使用預設優先順序


    while(1);

    // 等待執行緒的結束(實際上由於執行緒一直在迴圈中,所以main函式不會結束)
    pthread_join(tid0, &retval); // 等待執行緒的結束,並取返回值
    pthread_attr_destroy(&attr);
    pthread_join(tid1, &retval); // 等待執行緒的結束,並取返回值

}

設定執行緒的競爭作用域

執行緒的競爭作用域:表示執行緒間競爭CPU的範圍,也就是說執行緒優先順序的有效範圍。

c
int pthread_attr_setscope(pthread_attr_t*attr,int scope);
int pthread_attr_getscope(const pthread_attr_t*attr,int*scope);

描述:設定執行緒的競爭範圍。

使用前,需要將pthread_attr_setinheritsched設定為PTHREAD_EXPLICIT_SCHED

引數解析:

scope:

  • PTHREAD_SCOPE_SYSTEM :與系統中所有執行緒一起競爭CPU時間
  • PTHREAD_SCOPE_PROCESS:僅與同進程中的執行緒競爭CPU。

根據man pthread_attr_setscope的結果來看目前LinuxThreads僅實現了PTHREAD_SCOPE_SYSTEM一值。

Linux supportsPTHREAD_SCOPE_SYSTEM, but notPTHREAD_SCOPE_PROCESS

設定堆疊

執行緒可以設定堆疊地址(stackaddr)與大小(stacksize)。對於大部分程式是應該避免使用的。

如果使用attr建立多個執行緒,則呼叫方必須在對pthread_create()的呼叫之間更改堆疊地址屬性;否則,執行緒將嘗試為其堆疊使用相同的記憶體區域,隨後將出現混亂。

c
intpthread_attr_setstack(pthread_attr_t *attr,
                          void *stackaddr, size_t stacksize);
// 或者由這2個函式分別設定
intpthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
intpthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);



intpthread_attr_getstack(constpthread_attr_t *attr,
                          void **stackaddr, size_t *stacksize);
intpthread_attr_getstackaddr(constpthread_attr_t *attr, void **stackaddr);
intpthread_attr_getstacksize(constpthread_attr_t *attr, size_t *stacksize);

intpthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
intpthread_attr_getguardsize(constpthread_attr_t *attr, size_t *guardsize);

當應用程式使用pthread_attr_setstack()時,它將接管分配堆疊的責任;此時,pthread_attr_setguardsize()設定的任何保護大小值都將被忽略。如果認為有必要,應用程式有責任分配一個保護區(防止讀寫的一個或多個頁面)來處理堆疊溢位的可能性。

stackaddr中指定的地址應該適當對齊:為了完全可移植,請在頁面邊界(sysconf(_SC_PAGESIZE))上對齊它。posix_memalign()可用於分配。

stacksize也應該是系統頁面大小的倍數。

關於大小

預設情況下執行緒保留1M的,而且會在堆疊的頂增加一個空閒的記憶體頁,當訪問該記憶體頁的時候就會觸發SIGSEGV訊號,如果開發者設定了stack size那麼就需要使用者制定這個多餘的記憶體頁並且通過mprotect函式設定保護標誌,而且它必須設定呼叫pthread_attr_setdetachstate且設定PTHREAD_CREATE_JOINABLE模式,因為只有其他執行緒呼叫pthread_join後分配的資源才會被釋放, 執行緒的堆疊的分配必須大於一個最小值PTHREAD_STACK_MIN()。當分配內的時候會設定MAP_NORESERVE標誌(mmap),這個標誌表示不預留交換空間,當對該記憶體進行寫的時候,如果系統不能分配到交換空間,那麼就會觸發SIGSEGV訊號,如果可以分配到交換空間,那麼就會把private page複製到交換空間。如果mmap沒有指定MAP_NORESERVE,在分配空間的時候就會保留和對映區域相同大小的交換空間(這個其實就是資源的滯後分配原則)

關於地址

如果執行緒地址為NULL,那麼pthread分配指定的記憶體(1M)或者是指定的堆疊大小,如果設定了堆疊的地址那麼記憶體的分配必須由開發者設定,例如:

c
stackbase = (void *) malloc(size);
ret = pthread_attr_setstacksize(&tattr, size);
ret = pthread_attr_setstackaddr(&tattr, stackbase);
ret = pthread_create(&tid, &tattr, func, arg);

affinity 設定執行緒親和性

執行緒的親和性

CPU 親和性(affinity) 就是程序要在某個給定的 CPU 上儘量長時間地執行而不被遷移到其他處理器的傾向性。Linux 核心程序排程器天生就具有被稱為 軟 CPU 親和性(affinity) 的特性,這意味著程序通常不會在處理器之間頻繁遷移。這種狀態正是我們希望的,因為程序遷移的頻率小就意味著產生的負載小。

親和性分為軟親和性與硬親和性2種:

  • 軟親和性 :程序並不會在處理器之間頻繁遷移
  • 硬親和性:程序需要在您指定的處理器上執行

CPU的數量與表示

在有n個CPU的Linux上,CPU是用0...n-1來進行一一標識的。

CPU的數量可以通過proc檔案系統下的CPU相關檔案得到,如cpuinfo和stat:

bash
cat /proc/stat | grep "^cpu[0-9]\+" | wc -l

在系統程式設計中,可以直接呼叫庫呼叫sysconf獲得:sysconf(_SC_NPROCESSORS_ONLN);

c
#define  _GNU_SOURCE

intpthread_attr_setaffinity_np(pthread_attr_t *attr,
                                size_t cpusetsize, constcpu_set_t *cpuset);
intpthread_attr_getaffinity_np(constpthread_attr_t *attr,
                                size_t cpusetsize, cpu_set_t *cpuset);

/* 執行時設定 */
intpthread_setaffinity_np(pthread_t thread, size_t cpusetsize,
                           constcpu_set_t *cpuset);
intpthread_getaffinity_np(pthread_t thread, size_t cpusetsize,
                           cpu_set_t *cpuset);

/*
	cpu_set_t:可以理解為一個CPU的集合,通過約定好的巨集進行清除,設定以及判斷
*/

voidCPU_ZERO(cpu_set_t  *set); //(初始化操作)
voidCPU_SET(int cpu,cpu_set_t *set)//(將某個cpu加進cpu集裡)voidCPU_CLR(int cpu,cpu_set_t *set);//(將某個cpu清除出cpu集裡)
voidCPU_ISSET(int cpu,constcpu_set_t *set);// (判斷某個cpu是不是在cpu集裡)

在 Linux 核心中,所有的程序都有一個相關的資料結構,稱為task_struct。這個結構非常重要,原因有很多;其中與 親和性(affinity)相關度最高的是cpus_allowed位掩碼。這個位掩碼由 n 位組成,與系統中的 n 個邏輯處理器一一對應。 具有 4 個物理 CPU 的系統可以有 4 位。如果這些 CPU 都啟用了超執行緒,那麼這個系統就有一個 8 位的位掩碼。

如果為給定的程序設定了給定的位,那麼這個程序就可以在相關的 CPU 上執行。因此,如果一個程序可以在任何 CPU 上執行,並且能夠根據需要在處理器之間進行遷移,那麼位掩碼就全是 1。實際上,這就是 Linux 中程序的預設狀態。

例程

c
#define _GNU_SOURCE
#include<stdio.h>
#include<math.h>
#include<pthread.h>
cpu_set_t cpuset,cpuget;
doublewaste_time(long n){
    double res = 0;
    long i = 0;
    while (i <n * 200000000) {
        i++;
        res += sqrt(i);
    }
    return res;
}

void *thread_func(void *param){   
    CPU_ZERO(&cpuset);
    CPU_SET(0, &cpuset); /* cpu 0 is in cpuset now */
    /* bind process to processor 0 */
    if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) !=0) {
        perror("pthread_setaffinity_np");
    }  
    printf("Core 0 is running!\n");
    /* waste some time so the work is visible with "top" */
    printf("result: %f\n", waste_time(5));
    pthread_exit(NULL);
}


intmain(int argc, char *argv[]){ 
    pthread_t my_thread;
    time_t startwtime, endwtime;
    startwtime = time (NULL); 
    if (pthread_create(&my_thread, NULL, thread_func,NULL) != 0) {
        perror("pthread_create");
    }
    pthread_join(my_thread,NULL);
    endwtime = time (NULL);
    printf ("wall clock time = %d\n", (endwtime - startwtime));
    return 0;
}

背景

上一講我們介紹了執行緒的建立,回收與銷燬;簡單地提到了執行緒屬性。這一講我們就來具體看看,執行緒的屬性。

概述

c
#include<pthread.h>

typedef struct __pthread_attr_s
{
    int                       __detachstate;   // 執行緒的分離狀態
    int                       __schedpolicy;   // 執行緒排程策略
    structsched_param         __schedparam;    // 執行緒的排程引數
    int                       __inheritsched;  // 執行緒的繼承性
    int                       __scope;         // 執行緒的作用域
    size_t                    __guardsize;     // 執行緒棧末尾的警戒緩衝區大小
    int                       __stackaddr_set; // 執行緒的棧設定
    void*                     __stackaddr;     // 執行緒棧的位置
    size_t                    __stacksize;     // 執行緒棧的大小
} pthread_attr_t;

intpthread_attr_init(pthread_attr_t *attr);
intpthread_attr_destroy(pthread_attr_t *attr);

執行緒具有屬性,用pthread_attr_t表示,在對該結構進行處理之前必須進行初始化(pthread_attr_init),在使用後需要對其去除初始化(pthread_attr_destroy)。

初始化為預設屬性

c
intpthread_attr_init(pthread_attr_t *attr);

描述:初始化一個執行緒屬性物件,重置為當前系統支援執行緒的所有屬性的預設值。

屬性
__scope PTHREAD_SCOPE_PROCESS
__tetachstate PTHREAD_CREATE_JOINABL
__stackaddr NULL
__stacksize 1M
__sched_param.priority 0
__inheritsched PTHREAD_INHERIT_SCHED
__schedpolicy SCHED_OTHER

反初始化

c
intpthread_attr_destroy(pthread_attr_t *attr);

描述:銷燬一個執行緒屬性物件,使它在重新初始化之前不能重新使用。

原理:用無效的值設定了屬性物件。

因此:如果經pthread_attr_destroy去除初始化之後的pthread_attr_t結構被pthread_create函式呼叫,將會導致其返回錯誤。

detachstate 分離狀態

我們來分析結構體中的有關成員。

c
intpthread_attr_setdetachstate(pthread_attr_t* attr, int detachstate);

// 有set就有get 。
intpthread_attr_getdetachstate(constpthread_attr_t* attr, int *detachstate)

描述:設定執行緒是否和其他執行緒分離(能否呼叫pthread_join()回收), 執行時可以呼叫pthread_detach()完成。

c
intpthread_detach(pthread_t thread);

引數解析:

attr:設定的屬性物件

detachstate :分離狀態

  • PTHREAD_CREATE_JOINABLE(預設):執行緒的資源在退出後自行釋放。
  • PTHREAD_CREATE_DETACHED:

設定為PTHREAD_CREATE_DETACH狀態(不論是建立時設定還是執行時設定) 則不能再恢復到PTHREAD_CREATE_JOINABLE狀態。

返回值:成功返回0,失敗返回錯誤號。

schedpolicy 排程策略與優先順序

排程策略

如果主執行緒是唯一的執行緒,那麼基本上不會被排程出去。另一方面,如果可執行的執行緒數大於CPU的數量,那麼作業系統最終會將某個正在執行的執行緒排程出去,從而使其他執行緒能夠使用CPU。這將導致一次上下文切換。在這個過程中將儲存當前執行執行緒的執行上下文,並將新排程進來的執行緒的執行上下文設定為當前上下文。

Linux核心的三種排程策略:

  • SCHED_OTHER 分時排程策略,預設的排程策略
  • SCHED_FIFO 實時排程策略,先到先服務。一旦佔用cpu則一直執行。一直執行直到有更高優先順序任務到達或自己放棄
  • SCHED_RR 實時排程策略,時間片輪轉。當程序的時間片用完,系統將重新分配時間片,並置於就緒佇列尾。放在佇列尾保證了所有具有相同優先順序的RR任務的排程公平

繼承建立者的排程策略

只有在不繼承時,下面的操作才是有效的。

c
intpthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
intpthread_attr_getinheritsched(constpthread_attr_t *attr, int *inheritsched);

描述:設定執行緒是否繼承建立者優先順序屬性

引數解析:

inheritsched : 是否繼承

  • PTHREAD_INHERIT_SCHED 繼承
  • PTHREAD_EXPLICIT_SCHED 不繼承

獲取可設定的優先順序

c
intsched_get_priority_max(int policy);
intsched_get_priority_min(int policy);

描述:獲取本執行緒的最大/小優先順序。

返回值:成功時返回最大/小值,失敗返回-1。

設定執行緒的排程策略與優先順序

c
intpthread_setschedparam(pthread_t thread, int policy,
                          const struct sched_param *param);
intpthread_getschedparam(pthread_t thread, int *policy,
                          struct sched_param *param);
/* 用到的結構體 */
structsched_param {
    int sched_priority;     /* Scheduling priority */
};

描述:設定執行緒的排程策略, 執行時可以呼叫pthread_setschedparam()來改變。

引數解析:

policy:

  • (預設)SCHED_OTHER(正常、非實時)
  • SCHED_RR(實時、輪轉法)
  • SCHED_FIFO(實時、先入先出)

param:優先順序(越大越高)

優先順序與排程的例程

c
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>

void *task0(void *arg){
    while(1)
    {
        //sleep(1);
        printf("task0.\n");
    }
}

void *task1(void *arg){
    while(1)
    {
        sleep(1);
        printf("task1.\n");
    }
}

intmain(void){
    pthread_attr_t attr;		
    structsched_paramparm;
    pthread_t tid0, tid1;
    void* retval;

    pthread_attr_init(&attr);

    pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); //不繼承建立者的排程策略,而是設定以下的排程
    pthread_attr_setschedpolicy(&attr, SCHED_FIFO); //為執行緒屬性設定排程策略

    parm.sched_priority = 1;					// 設定執行緒優先順序
    pthread_attr_setschedparam(&attr,&parm);	// 設定執行緒優先順序

    pthread_create(&tid0, &attr, task0, NULL);  // 為執行緒task0設定優先順序
    pthread_create(&tid1, NULL , task1, NULL);  // 讓執行緒task1使用預設優先順序


    while(1);

    // 等待執行緒的結束(實際上由於執行緒一直在迴圈中,所以main函式不會結束)
    pthread_join(tid0, &retval); // 等待執行緒的結束,並取返回值
    pthread_attr_destroy(&attr);
    pthread_join(tid1, &retval); // 等待執行緒的結束,並取返回值

}

設定執行緒的競爭作用域

執行緒的競爭作用域:表示執行緒間競爭CPU的範圍,也就是說執行緒優先順序的有效範圍。

c
int pthread_attr_setscope(pthread_attr_t*attr,int scope);
int pthread_attr_getscope(const pthread_attr_t*attr,int*scope);

描述:設定執行緒的競爭範圍。

使用前,需要將pthread_attr_setinheritsched設定為PTHREAD_EXPLICIT_SCHED

引數解析:

scope:

  • PTHREAD_SCOPE_SYSTEM :與系統中所有執行緒一起競爭CPU時間
  • PTHREAD_SCOPE_PROCESS:僅與同進程中的執行緒競爭CPU。

根據man pthread_attr_setscope的結果來看目前LinuxThreads僅實現了PTHREAD_SCOPE_SYSTEM一值。

Linux supportsPTHREAD_SCOPE_SYSTEM, but notPTHREAD_SCOPE_PROCESS

設定堆疊

執行緒可以設定堆疊地址(stackaddr)與大小(stacksize)。對於大部分程式是應該避免使用的。

如果使用attr建立多個執行緒,則呼叫方必須在對pthread_create()的呼叫之間更改堆疊地址屬性;否則,執行緒將嘗試為其堆疊使用相同的記憶體區域,隨後將出現混亂。

c
intpthread_attr_setstack(pthread_attr_t *attr,
                          void *stackaddr, size_t stacksize);
// 或者由這2個函式分別設定
intpthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
intpthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);



intpthread_attr_getstack(constpthread_attr_t *attr,
                          void **stackaddr, size_t *stacksize);
intpthread_attr_getstackaddr(constpthread_attr_t *attr, void **stackaddr);
intpthread_attr_getstacksize(constpthread_attr_t *attr, size_t *stacksize);

intpthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
intpthread_attr_getguardsize(constpthread_attr_t *attr, size_t *guardsize);

當應用程式使用pthread_attr_setstack()時,它將接管分配堆疊的責任;此時,pthread_attr_setguardsize()設定的任何保護大小值都將被忽略。如果認為有必要,應用程式有責任分配一個保護區(防止讀寫的一個或多個頁面)來處理堆疊溢位的可能性。

stackaddr中指定的地址應該適當對齊:為了完全可移植,請在頁面邊界(sysconf(_SC_PAGESIZE))上對齊它。posix_memalign()可用於分配。

stacksize也應該是系統頁面大小的倍數。

關於大小

預設情況下執行緒保留1M的,而且會在堆疊的頂增加一個空閒的記憶體頁,當訪問該記憶體頁的時候就會觸發SIGSEGV訊號,如果開發者設定了stack size那麼就需要使用者制定這個多餘的記憶體頁並且通過mprotect函式設定保護標誌,而且它必須設定呼叫pthread_attr_setdetachstate且設定PTHREAD_CREATE_JOINABLE模式,因為只有其他執行緒呼叫pthread_join後分配的資源才會被釋放, 執行緒的堆疊的分配必須大於一個最小值PTHREAD_STACK_MIN()。當分配內的時候會設定MAP_NORESERVE標誌(mmap),這個標誌表示不預留交換空間,當對該記憶體進行寫的時候,如果系統不能分配到交換空間,那麼就會觸發SIGSEGV訊號,如果可以分配到交換空間,那麼就會把private page複製到交換空間。如果mmap沒有指定MAP_NORESERVE,在分配空間的時候就會保留和對映區域相同大小的交換空間(這個其實就是資源的滯後分配原則)

關於地址

如果執行緒地址為NULL,那麼pthread分配指定的記憶體(1M)或者是指定的堆疊大小,如果設定了堆疊的地址那麼記憶體的分配必須由開發者設定,例如:

c
stackbase = (void *) malloc(size);
ret = pthread_attr_setstacksize(&tattr, size);
ret = pthread_attr_setstackaddr(&tattr, stackbase);
ret = pthread_create(&tid, &tattr, func, arg);

affinity 設定執行緒親和性

執行緒的親和性

CPU 親和性(affinity) 就是程序要在某個給定的 CPU 上儘量長時間地執行而不被遷移到其他處理器的傾向性。Linux 核心程序排程器天生就具有被稱為 軟 CPU 親和性(affinity) 的特性,這意味著程序通常不會在處理器之間頻繁遷移。這種狀態正是我們希望的,因為程序遷移的頻率小就意味著產生的負載小。

親和性分為軟親和性與硬親和性2種:

  • 軟親和性 :程序並不會在處理器之間頻繁遷移
  • 硬親和性:程序需要在您指定的處理器上執行

CPU的數量與表示

在有n個CPU的Linux上,CPU是用0...n-1來進行一一標識的。

CPU的數量可以通過proc檔案系統下的CPU相關檔案得到,如cpuinfo和stat:

bash
cat /proc/stat | grep "^cpu[0-9]\+" | wc -l

在系統程式設計中,可以直接呼叫庫呼叫sysconf獲得:sysconf(_SC_NPROCESSORS_ONLN);

c
#define  _GNU_SOURCE

intpthread_attr_setaffinity_np(pthread_attr_t *attr,
                                size_t cpusetsize, constcpu_set_t *cpuset);
intpthread_attr_getaffinity_np(constpthread_attr_t *attr,
                                size_t cpusetsize, cpu_set_t *cpuset);

/* 執行時設定 */
intpthread_setaffinity_np(pthread_t thread, size_t cpusetsize,
                           constcpu_set_t *cpuset);
intpthread_getaffinity_np(pthread_t thread, size_t cpusetsize,
                           cpu_set_t *cpuset);

/*
	cpu_set_t:可以理解為一個CPU的集合,通過約定好的巨集進行清除,設定以及判斷
*/

voidCPU_ZERO(cpu_set_t  *set); //(初始化操作)
voidCPU_SET(int cpu,cpu_set_t *set)//(將某個cpu加進cpu集裡)voidCPU_CLR(int cpu,cpu_set_t *set);//(將某個cpu清除出cpu集裡)
voidCPU_ISSET(int cpu,constcpu_set_t *set);// (判斷某個cpu是不是在cpu集裡)

在 Linux 核心中,所有的程序都有一個相關的資料結構,稱為task_struct。這個結構非常重要,原因有很多;其中與 親和性(affinity)相關度最高的是cpus_allowed位掩碼。這個位掩碼由 n 位組成,與系統中的 n 個邏輯處理器一一對應。 具有 4 個物理 CPU 的系統可以有 4 位。如果這些 CPU 都啟用了超執行緒,那麼這個系統就有一個 8 位的位掩碼。

如果為給定的程序設定了給定的位,那麼這個程序就可以在相關的 CPU 上執行。因此,如果一個程序可以在任何 CPU 上執行,並且能夠根據需要在處理器之間進行遷移,那麼位掩碼就全是 1。實際上,這就是 Linux 中程序的預設狀態。

例程

c
#define _GNU_SOURCE
#include<stdio.h>
#include<math.h>
#include<pthread.h>
cpu_set_t cpuset,cpuget;
doublewaste_time(long n){
    double res = 0;
    long i = 0;
    while (i <n * 200000000) {
        i++;
        res += sqrt(i);
    }
    return res;
}

void *thread_func(void *param){   
    CPU_ZERO(&cpuset);
    CPU_SET(0, &cpuset); /* cpu 0 is in cpuset now */
    /* bind process to processor 0 */
    if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) !=0) {
        perror("pthread_setaffinity_np");
    }  
    printf("Core 0 is running!\n");
    /* waste some time so the work is visible with "top" */
    printf("result: %f\n", waste_time(5));
    pthread_exit(NULL);
}


intmain(int argc, char *argv[]){ 
    pthread_t my_thread;
    time_t startwtime, endwtime;
    startwtime = time (NULL); 
    if (pthread_create(&my_thread, NULL, thread_func,NULL) != 0) {
        perror("pthread_create");
    }
    pthread_join(my_thread,NULL);
    endwtime = time (NULL);
    printf ("wall clock time = %d\n", (endwtime - startwtime));
    return 0;
}