2016-02-04 57 views
1

我希望有人能幫助我。在過去的十年中,我沒有用C語言編寫太多的代碼,並且在兩天之前就選擇了這個代碼,所以請耐心等待,因爲我很生疏。謝謝!C代碼堆棧損壞改變變量

什麼:

我正在創建一個應用程序非常簡單的線程池。這段代碼是用編譯器的GNU GCC編寫在C代碼塊上的。它是作爲命令行應用程序構建的。沒有附加文件被鏈接或包含。

代碼應該創建X個線程(在這種情況下,我將它設置爲10),每個線程在觀察數組條目(由線程線程索引或計數所標識的數組條目)時都會坐着並等待它可能需要的任何傳入數據處理。一旦給定的孩子處理了通過數組進入的數據,就不需要將數據傳遞迴主線程;相反,孩子應該簡單地將該數組條目重置爲0以表示它已準備好處理另一個輸入。主線程將接收請求,並將它們分發給任何可用的線程。如果沒有可用的,那麼它將拒絕處理該輸入。

爲了簡單起見,下面的代碼是確實表現出我試圖追查堆棧溢出一個完整的工作,但修剪和去內臟的版本。編譯沒有問題,最初運行正常但也有少數通過在子線程過程中threadIndex值之後(的WorkerThread)被損壞並跳轉到怪異的價值觀 - 通常成爲我已經把在「睡眠」功能的毫秒數。

我已經檢查:

  • 的threadIndex變量不是全局或共享變量。
  • 所有的數組都是足夠大到足以處理線程,我創建的最大數量。
  • 所有迴路有loopvariable重置爲0之前運行。
  • 我還沒有命名具有相同名稱的多個變量。
  • 我用atomic_load,以確保我不會在一次寫入同一個全局數組變量有兩個不同的線程請注意我生疏......我可誤解這部分是如何工作的
  • 我已經把測試用例遍歷,看看變量發生什麼變化,我很難過。

最佳推測

所有我的研究證實了我從幾年前召回;我可能會在某個地方出界並導致堆棧損壞。我已經在google和堆棧溢出中看到了許多其他類似的問題,同時我也指出了相同的結論,我一直無法弄清楚我的代碼中有什麼特別的錯誤。

#include<stdio.h> 
//#include<string.h> 
#include<pthread.h> 
#include<stdlib.h> 
#include<conio.h> 
//#include<unistd.h> 
#define ESCAPE 27 

int maxThreads = 10; 
pthread_t tid[21]; 
int ret[21]; 
int threadIncoming[21]; 
int threadRunning[21]; 

struct arg_struct { 
    char* arg1; 
    int arg2; 
}; 
//sick of the stupid upper/lowercase nonsense... boom... fixed 
void* sleep(int time){Sleep(time);} 


void* workerThread(void *arguments) 
{ 
    //get the stuff passed in to us 
    struct arg_struct *args = (struct arg_struct *)arguments; 
    char *address = args -> arg1; 
    int threadIndex = args -> arg2; 
    //hold how many we have processed - we are unlikely to ever hit the max so no need to round robin this number at this point 
    unsigned long processedCount = 0; 
    //this never triggers so it IS coming in correctly 
    if(threadIndex > 20){ 
     printf("INIT ERROR! ThreadIndex = %d", threadIndex); 
     sleep(1000); 
    } 
    unsigned long x = 0; 
    pthread_t id = pthread_self(); 
    //as long as we should be running 
    while(__atomic_load_n (&threadRunning[threadIndex], __ATOMIC_ACQUIRE)){ 
     //if and only if we have something to do... 
     if(__atomic_load_n (&threadIncoming[threadIndex], __ATOMIC_ACQUIRE)){ 


      //simulate us doing something 
      //for(x=0; x<(0xFFFFFFF);x++); 
      sleep(2001); 
      //the value going into sleep is CLEARLY somehow ending up in index because you can change that to any number you want 
      //and next thing you know the next line says "First thread processing done on (the value given to sleep) 
      printf("\n First thread processing done on %d\n", threadIndex); 




      //all done doing something so clear the incoming so we can reuse it for our next one 
      //this error should not EVER be able to get thrown but it is.... something is corrupting our stack and going into memory that it shouldn't 
      if(threadIndex > 20){ printf("ERROR! ThreadIndex = %d", threadIndex); } 
      else{ __atomic_store_n (&threadIncoming[threadIndex], 0, __ATOMIC_RELEASE); } 
      //increment the processed count 
      ++processedCount; 
     } 
     else{Sleep(10);} 
    } 
    //no need to do atomocity I don't think for this as it is only set on the exit and not read till after everything is done 
    ret[threadIndex] = processedCount; 
    pthread_exit(&ret[threadIndex]); 
    return NULL; 
} 



int main(void) 
{ 
    int i = 0; 
    int err; 
    int *ptr[21]; 
    int doLoop = 1; 
    //initialize these all to set the threads to running and the status on incoming to NOT be processing 
    for(i=0;i < maxThreads;i++){ 
      threadIncoming[i] = 0; 
      threadRunning[i] = 1; 
    } 
    //create our threads 
    for(i=0;i < maxThreads;i++) 
    { 
     struct arg_struct args; 
     args.arg1 = "here"; 
     args.arg2 = i; 
     err = pthread_create(&(tid[i]), NULL, &workerThread, (void *)&args); 
     if (err != 0){ printf("\ncan't create thread :[%s]", strerror(err)); } 
    } 
    //loop until we hit escape 
    while(doLoop){ 
     //see if we were pressed escape 
     if(kbhit()){ if(getch() == ESCAPE){ doLoop = 0; } } 
     //just for testing - actual version would load only as needed 
     for(i=0;i < maxThreads;i++){ 
      //make sure we synchronize so we don't end up pointing into a garbage address or half loading when a thread accesses us or whatever was going on 
      if(!__atomic_load_n (&threadIncoming[i], __ATOMIC_ACQUIRE)){ 
       __atomic_store_n (&threadIncoming[i], 1, __ATOMIC_RELEASE); 
      } 
     } 
    } 
    //exiting... 
    printf("\n'Esc' pressed. Now exiting...\n"); 
    //call to end them all... 
    for(i=0;i < maxThreads;i++){ __atomic_store_n (&threadRunning[i], 0, __ATOMIC_RELEASE); } 
    //join them all back up - if we had an actual worthwhile value here we could use it 
    for(i=0;i < maxThreads;i++){ 
     pthread_join(tid[i], (void**)&(ptr[i])); 
     printf("\n return value from thread %d is [%d]\n", i, *ptr[i]); 
    } 
    return 0; 
} 

輸出

這裏是輸出我得到。請注意,在開始瘋狂之前需要多長時間,似乎可能會有所不同,但不會太多。

Output Screen with Error

+0

'一個非常簡單的線程池' - 看起來並不那麼簡單。通常,「不簡單」意味着'不能正常工作',尤其是當應用於mutithreaded應用程序時。大多數實際上簡單的線程池將任務排列到工作線程,這些線程圍繞阻塞的生產者 - 消費者隊列進行循環。這是衆所周知的設計,運行良好,不需要浪費CPU輪詢,延遲添加Sleep()循環或任何類型的「管理線程」。在數組中微管理線程實例是複雜的,浪費的,幾乎肯定會出錯。果然...... :) –

+1

'else {Sleep(10);}'這似乎有問題,它爲什麼在那裏? [在多線程應用程序中使用Sleep(n)](http://www.flounder.com/badprogram.htm#Sleep)。 – Lundin

+0

^^是的,大部分嘗試避免/減輕一些其他問題,可能投票CPU使用:(無論如何,它肯定是誤用睡眠()。 –

回答

6

我不信任你的args處理,似乎有一個競爭條件。如果您在第一個線程運行之前創建了N個線程會怎麼樣?然後,第一個創建的線程可能會在第N個線程中看到args,而不是第一個線程,依此類推。

我不相信有一種保證,像這樣的循環中使用的自動變量是在非重疊區域中創建的;畢竟它們在循環的每次迭代中都超出了範圍。

+2

當你通過一個線程的變量的地址,你需要確保變量保持有效,並具有正確的值爲只要線程可能需要它O操作。這不是在這裏完成的。 –

+0

^^ @DavidSchwartz說。 OP設計是imp ..很難得到正確和高性能。 –

+0

好的,我明白你在說我相信。上次我做了這些事情是在2003年,所以它有點 - 沒有驚訝,我犯了一個愚蠢的錯誤。 這意味着理論上所有的線程都可以訪問其他'args'變量,或者更糟的是它可能在線程啓動時被破壞。看來我更好的選擇是將'args'變量從單個變量更改爲一個數組,以便每個線程傳遞一個指向該數組索引的指針而不是實際的共享變量? – Jason