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
)。
初始化為預設屬性
cintpthread_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 |
反初始化
cintpthread_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()
完成。
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任務的排程公平
繼承建立者的排程策略
只有在不繼承時,下面的操作才是有效的。
cintpthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
intpthread_attr_getinheritsched(constpthread_attr_t *attr, int *inheritsched);
描述:設定執行緒是否繼承建立者優先順序屬性
引數解析:
inheritsched : 是否繼承
- PTHREAD_INHERIT_SCHED 繼承
- PTHREAD_EXPLICIT_SCHED 不繼承
獲取可設定的優先順序
cintsched_get_priority_max(int policy);
intsched_get_priority_min(int policy);
描述:獲取本執行緒的最大/小優先順序。
返回值:成功時返回最大/小值,失敗返回-1。
設定執行緒的排程策略與優先順序
cintpthread_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的範圍,也就是說執行緒優先順序的有效範圍。
cint 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 supports
PTHREAD_SCOPE_SYSTEM
, but notPTHREAD_SCOPE_PROCESS
設定堆疊
執行緒可以設定堆疊地址(stackaddr)與大小(stacksize)。對於大部分程式是應該避免使用的。
如果使用attr建立多個執行緒,則呼叫方必須在對pthread_create()
的呼叫之間更改堆疊地址屬性;否則,執行緒將嘗試為其堆疊使用相同的記憶體區域,隨後將出現混亂。
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)或者是指定的堆疊大小,如果設定了堆疊的地址那麼記憶體的分配必須由開發者設定,例如:
cstackbase = (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:
bashcat /proc/stat | grep "^cpu[0-9]\+" | wc -l
在系統程式設計中,可以直接呼叫庫呼叫sysconf獲得:sysconf(_SC_NPROCESSORS_ONLN);
#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
)。
初始化為預設屬性
cintpthread_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 |
反初始化
cintpthread_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()
完成。
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任務的排程公平
繼承建立者的排程策略
只有在不繼承時,下面的操作才是有效的。
cintpthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
intpthread_attr_getinheritsched(constpthread_attr_t *attr, int *inheritsched);
描述:設定執行緒是否繼承建立者優先順序屬性
引數解析:
inheritsched : 是否繼承
- PTHREAD_INHERIT_SCHED 繼承
- PTHREAD_EXPLICIT_SCHED 不繼承
獲取可設定的優先順序
cintsched_get_priority_max(int policy);
intsched_get_priority_min(int policy);
描述:獲取本執行緒的最大/小優先順序。
返回值:成功時返回最大/小值,失敗返回-1。
設定執行緒的排程策略與優先順序
cintpthread_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的範圍,也就是說執行緒優先順序的有效範圍。
cint 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 supports
PTHREAD_SCOPE_SYSTEM
, but notPTHREAD_SCOPE_PROCESS
設定堆疊
執行緒可以設定堆疊地址(stackaddr)與大小(stacksize)。對於大部分程式是應該避免使用的。
如果使用attr建立多個執行緒,則呼叫方必須在對pthread_create()
的呼叫之間更改堆疊地址屬性;否則,執行緒將嘗試為其堆疊使用相同的記憶體區域,隨後將出現混亂。
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)或者是指定的堆疊大小,如果設定了堆疊的地址那麼記憶體的分配必須由開發者設定,例如:
cstackbase = (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:
bashcat /proc/stat | grep "^cpu[0-9]\+" | wc -l
在系統程式設計中,可以直接呼叫庫呼叫sysconf獲得:sysconf(_SC_NPROCESSORS_ONLN);
#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;
}