2011-03-15 49 views
9

我想在非線程模式下正常工作的GCC C應用程序中使用線程(第一次!)。當我運行它時,一些線程給出的結果全部爲零而不是所需的答案(我知道用於檢查目的),但是每次運行它時給出零的線程都不相同。給出非零答案的答案是正確的,所以代碼似乎運行正常。我想知道是否有人可以指出哪些地方我可能有不是線程安全的東西。線程能否在不鎖定的情況下寫入相同結構數組的不同元素?

我自己的想法是可能是由於我如何收集結果或可能是內存分配 - 我使用malloc和免費但在StackOverflow其他地方我看到GCC malloc被認爲是線程安全的,如果鏈接-lpthread(我是這樣做)。沒有使用全局/靜態變量 - 一切都作爲函數參數傳遞。

爲了將結果回傳給main,我的線程例程使用了一個結構數組。每個線程寫入此數組的一個獨特元素,因此它們不會嘗試寫入相同的內存。也許我在寫結果時需要使用某種形式的鎖定,即使它們沒有轉到結構數組的相同元素上?

我跟着線程代碼這裏的食譜: https://computing.llnl.gov/tutorials/pthreads/#Abstract

我重視的情況下,(簡化)代碼提取這使任何線索(我可以省略/修改一些錯誤,但我沒有要求任何人被發現錯誤,只是一般的方法)。

typedef struct p_struct { /* used for communicating results back to main */ 
    int given[CELLS]; 
    int type; 
    int status; 
    /*... etc */ 
} puzstru; 

typedef struct params_struct { /* used for calling generate function using threads */ 
    long seed; 
    char *text; 
    puzzle *puzzp; 
    bool unique; 
    int required; 
} paramstru; 
/* ========================================================================================== */ 
void *myfunc(void *spv) /* calling routine for use by threads */ 
{ 
    paramstru *sp=(paramstru *)spv; 
    generate(sp->seed, sp->text, sp->puzzp, sp->unique, sp->required); 
    pthread_exit((void*) spv); 
} 
/* ========================================================================================== */ 
int generate(long seed, char *text, puzstru *puzzp, bool unique, int required) 
{ 
/* working code , also uses malloc and free, 
    puts results in the element of a structure array pointed to by "puzzp", 
    which is different for each thread 
    (see calling routine below :  params->puzzp=puz+thr;) 
    extract as follows: */ 
      puzzp->given[ix]=calcgiven[ix]; 
      puzzp->type=1; 
      puzzp->status=1; 
      /* ... etc */ 
} 
/* ========================================================================================== */ 


int main(int argc, char* argv[]) 
{ 
    pthread_t thread[NUM_THREADS]; 
    pthread_attr_t threadattr; 
    int thr,threadretcode; 
    void *threadstatus; 
    paramstru params[1]; 

    /* ....... ETC */ 

/* set up params structure for function calling parameters */ 
    params->text=mytext; 
    params->unique=TRUE; 
    params->required=1; 

    /* Initialize and set thread detached attribute */ 
    pthread_attr_init(&threadattr); 
    pthread_attr_setdetachstate(&threadattr, PTHREAD_CREATE_JOINABLE); 

    for(thr=0; thr<NUM_THREADS; thr++) 
    { 
     printf("Main: creating thread %d\n", thr); 
     params->seed=ran_arr_next(startingseeds); 
     params->puzzp=puz+thr; 
     threadretcode = pthread_create(&thread[thr], &threadattr, myfunc, (void *)params); 
     if (threadretcode) 
     { 
      printf("ERROR; return code from pthread_create() is %d\n", threadretcode); 
      exit(-1); 
     } 
    } 

    /* Free thread attribute and wait for the other threads */ 
    pthread_attr_destroy(&threadattr); 
    for(thr=0; thr<NUM_THREADS; thr++) 
    { 
     threadretcode = pthread_join(thread[thr], &threadstatus); 
     if (threadretcode) 
     { 
      printf("ERROR; return code from pthread_join() is %d\n", threadretcode); 
      exit(-1); 
     } 
     printf("Main: completed join with thread %d having a status of %ld\n",thr,(long)threadstatus); 
    } 

/* non-threaded code, print results etc ............. */ 

    free(startingseeds); 
    free(puz); 
    printf("Main: program completed. Exiting.\n"); 
    pthread_exit(NULL); 
} 

對於其他人閱讀此的利益 - 所有的答案是正確的,而問題的答案在標題是YES,線程可以安全地寫入結構的同一陣列的不同元素,我的問題是在調用程序 - 以下是修訂後的代碼片段(現在正常工作):

paramstru params[NUM_THREADS]; 

    for(thr=0; thr<NUM_THREADS; thr++) 
    { 
     printf("Main: creating thread %d\n", thr); 
    /* set up params structure for function calling parameters */ 
     params[thr].text=mytext; 
     params[thr].unique=TRUE; 
     params[thr].required=1; 
     params[thr].seed=ran_arr_next(startingseeds); 
     params[thr].puzzp=puz+thr; 
     threadretcode = pthread_create(&thread[thr], &threadattr, myfunc, (void *)&params[thr]); 
     if (threadretcode) 
     { 
      printf("ERROR; return code from pthread_create() is %d\n", threadretcode); 
      exit(-1); 
     } 
    } 
+0

嗯。變量puz在哪裏申報?它是'謎題*'類型嗎?如何計算'ix'?我猜你的問題在你標記爲「工作代碼」的那個塊中。 ;) – 2011-03-15 18:45:25

回答

3
paramstru params[1]; 

的代碼通過相同的結構,所有線程。就在線程初始化循環覆蓋數據,一個線程應該工作:

for(thr=0; thr<NUM_THREADS; thr++) 
    { 
     printf("Main: creating thread %d\n", thr); 
     params->seed=ran_arr_next(startingseeds); /* OVERWRITE */ 
     params->puzzp=puz+thr; /* OVERWRITE */ 

非線程代碼工作的原因是因爲params結構改變之前每次調用myfunc()終止。

+1

謝謝,我想有一個線程讀取params和下一個迭代的設置循環之間的競賽。 – RussellG 2011-03-15 19:57:02

+0

您可以通過屏障(緩慢)或通過爲每個線程使用單獨的結構(略微浪費內存)來修復此種族。 – 2011-03-15 20:15:25

1

您只創建了參數結構的一個副本,並覆蓋它並將相同的地址傳遞給每個線程。不要'paramstru params[NUM_THREADS];

+0

是的,謝謝,這是我會做的。 – RussellG 2011-03-15 19:56:28

6

要回答你的問題,從不同的線程寫入同一個數組的不同元素而不鎖定是完全正確的。如果兩個線程在沒有同步(例如,鎖定)的情況下寫入相同的字節,則將僅存在data race

正如其他答案所指出的那樣,你的代碼被寫入的原因是因爲你將一個指針傳給同一個params對象到你的每個線程,然後你修改那個對象。您可能需要爲每個線程創建一個新的param

+3

雖然它很安全,但如果不仔細,可能會導致性能不佳。如果有多個線程繼續訪問同一緩存行上的陣列元素,則會產生沉重的緩存線反彈,這很昂貴。 – ninjalj 2011-03-15 19:26:19

+0

感謝有關不同字節的信息。我在爲我的問題尋找錯誤的地方。 – RussellG 2011-03-15 19:55:35

+0

你確定它保證安全嗎?對於不能執行小於字寫入的機器,寫入字節的機器是讀取 - 修改 - 寫入操作?我同意這樣的機器是不應該用於任何真實世界目的的病態廢話,但嚴格地說,我相信如果你聲稱完全可移植性,他們需要考慮...... – 2011-03-15 20:17:16

相關問題