2011-02-17 19 views
3

用參數創建線程的最有效方法是什麼?參數是一個結構,如果結構不能停留在父線程堆棧上,則有兩種解決方案。線程參數的高效快捷方式

動態內存分配

struct Arg{ 
    int x; 
    int y; 
}; 

void* my_thread(void* v_arg){ 
    Arg* arg = (Arg*) v_arg; 

    //... running 

    delete arg; 
    return NULL; 
} 

//Creating a thread 
void a_function(){ 
    Arg* arg = new Arg; 
    arg->x = 1; arg->y = 2; 

    pthread_t t; 
    pthread_create(&t, NULL, my_thread, arg); 
    pthread_detach(t); 
} 

用旗語

struct Arg{ 
    sem_t sem; 
    int x; 
    int y; 
}; 

void* my_thread(void* v_arg){ 
    Arg* arg = (Arg*) v_arg; 
    int arg_x = v_arg->x; 
    int arg_y = v_arg->y; 
    sem_post(&(v_arg->sem)); 

    //... running 

    return NULL; 
} 

//Creating a thread 
void a_function(){ 
    Arg arg; 
    arg.x = 1; arg.y = 2; 
    sem_init(&(arg.sem), 0, 0); 

    pthread_t t; 
    pthread_create(&t, NULL, my_thread, &arg); 
    pthread_detach(t); 

    sem_wait(&(arg.sem)); 
    sem_destroy(&(arg.sem)); 
} 

我與Linux和Windows上工作。

+0

這段代碼表明你正在使用一些C風格的語言,我從你在Linux中進行編程的事實推斷出它是C,但我無法確定。因爲它與問題高度相關,您能否用所用語言標記問題,和/或提供一些更具體的問題? – KeithS 2011-02-17 17:57:56

+1

@KeithS:在Linux上它肯定看起來像C或C++(他使用`pthreads`),但類似的代碼可以用於Windows,並且我記得他的問題在兩個平臺上都是有效的。 – Argote 2011-02-17 18:04:04

回答

2

在您發佈的代碼中,最有效的實現是使用堆分配(您的第一個示例)。原因是堆分配(使用new()或malloc)比上下文切換便宜得多。考慮什麼需要在第二個例子發生:

  1. 分配堆棧空間精氨酸
  2. 初始化semphore
  3. 啓動線程和開關背景
  4. 複製變量到新的堆棧
  5. 切換背景返回
  6. 摧毀信號燈
  7. 拆離螺紋
  8. 將上下文切換

或者,你的第一個例子:

  1. 分配堆空間精氨酸
  2. 啓動線程
  3. 拆離螺紋
  4. 將上下文切換
0

這取決於。如果你的結構不大,最好動態地分配它,以便最小化奇怪的同步調用。否則,如果你的結構很大,而且你爲一個內存很小的系統編寫代碼,最好使用信號量(甚至是condvar)。

0

原子操作解決方案。這是獲取記憶力的非常高速度的方式。

如果參數總是相同的大小,預先分配一堆。將pNext添加到您的結構中以將它們鏈接在一起。創建一個_pRecycle全局來將所有可用的鏈接作爲鏈接列表使用pNext鏈接它們。當你需要一個參數時,使用原子操作來CAS到垃圾清單頭部。完成後,使用原子操作將arg放回垃圾清單的頭部。

CAS是指類似__sync_bool_compare_and_swap這樣的成功返回1。

搶參數記憶:

while (1) { // concurrency loop 
    pArg->pNext = _pRecycle; 
    if (CAS(&_pRecycle, pArg, pArg->pNext)) // change _pRecycle to pArg if _pRecycle hasn't changed. 
    break; // success 
} 
// you have given the mem back 

有一個競爭條件如果有什麼用途和而另一個線程A點之間交換出去回收PARG:

while (1) { // concurrency loop 
    pArg = _pRecycle; // _pRecycle is the global ptr to the head of the available arguments 
    // POINT A 
    if (CAS(&_pRecycle, pArg->pNext, pArg)) // change pRecycle to next item if pRecycle hasn't changed. 
    break; // success 
    // POINT B 
} 
// you can now use pArg to pass arguments 

完成後回收的說法內存和B.如果你的工作需要很長時間來處理這不會是一個問題。否則,你需要對列表的頭部進行版本化...爲此,你需要能夠一次自動地改變兩件事情......工會結合64位CAS來拯救!

typedef union _RecycleList { 
    struct { 
    int iversion; 
    TArg *pHead; 
    } 
    unsigned long n64; // this value is iVersion and pHead at the same time! 
} TRecycleList; 

TRecycleList _Recycle; 

獲得MEM:

while (1) // concurrency loop 
{ 
    TRecycleList Old.n64 = _Recycle.n64; 
    TRecycleList New.n64 = Old.n64; 
    New.iVersion++; 
    pArg = New.pHead; 
    New.pHead = New.pHead->pNext; 
    if (CAS(&_Recycle.n64, New.n64, Old.n64)) // if version isnt changed we get mem 
    break; // success 
} 

把MEM回:

while (1) // concurrency loop 
{ 
    TRecycleList Old.n64 = _Recycle.n64; 
    TRecycleList New.n64 = Old.n64; 
    New.iVersion++; 
    pArg->pNext = New.pHead; 
    New.pHead = pArg; 
    if (CAS(&_Recycle.n64, New.n64, Old.n64)) // if version isnt changed we release mem 
    break; // success 
} 

由於時間99.9999999%沒有兩個線程將被執行的代碼在同搶內存時間,你會得到很好的表現。我們的測試顯示CAS只需設置_pRecycle = pRecycle-> pNext就可以慢到2x。 64位和128位CAS的速度與32位一樣快。基本上它尖叫。偶爾兩次線程實際競賽時,併發循環會執行兩次。一個人總會贏,所以比賽結果非常快。