我希望有人可以帮助我。十多年来我没有用C代码写很多东西,而且两天前刚刚回来了,所以请耐心等待,因为我生锈了。谢谢!
什么:
我正在为应用程序创建一个非常简单的线程池。此代码使用GNU GCC编译器在CodeBlocks上用C编写。它构建为命令行应用程序。没有链接或包含其他文件。
代码应该创建X个线程(在这种情况下,我将它设置为10),每个线程在查看数组条目(由线程线程索引或计数标识)时坐下并等待它可能需要处理的任何传入数据。一旦给定的子进程处理了通过数组进入的数据,就不需要将数据传递回主线程;相反,孩子应该简单地将该数组条目重置为0,以表明它已准备好处理另一个输入。主线程将接收请求,并将它们发送到任何可用的线程。如果没有,那么它将拒绝处理该输入。
为简单起见,下面的代码是一个完整且有效但经过修剪和去内脏的版本,它会显示我想要追踪的堆栈溢出。这个编译很好并且最初运行正常但是在几次传递之后,子线程进程(workerThread)中的threadIndex值变得损坏并跳转到奇怪的值 - 通常变成我为“睡眠”#39投入的毫秒数;功能。
我检查了什么:
最好的猜测
我的所有研究都证实了我多年前的回忆;我可能会在某个地方走出界限并导致堆栈损坏。我在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;
}
输出
这是我得到的输出。请注意,它开始疯狂之前需要多长时间似乎可能会有所不同但并不多。
答案 0 :(得分:6)
我不相信你对args
的处理,似乎有竞争条件。如果在第一个线程运行之前创建N个线程怎么办?然后创建的第一个线程可能会看到第N个线程的args
,而不是第一个线程,依此类推。
我不相信这样可以保证像这样的循环中使用的自动变量是在非重叠区域中创建的;毕竟它们在循环的每次迭代中都超出了范围。