Linux – 强制单线程执行并用pthreaddebuggingmultithreading

我正在用C,pthread和Linuxdebugging一个multithreading的问题。 在我的MacOS 10.5.8上,C2D运行良好,在我的Linux计算机上(2-4个内核),它会产生不需要的输出。

我没有经验,所以我附上我的代码。 这很简单:每个新线程创build两个线程,直到达到最大值。 所以没什么大不了的,就像我前几天想的那样。 我可以强制单核执行,以防止我的错误发生?

我分析了程序执行,使用Valgrind进行testing:

valgrind --tool=drd --read-var-info=yes --trace-mutex=no ./threads 

我在BSS领域遇到了一些冲突,这些冲突是由我的全局结构和线程计数variables引起的。 不过,我可以通过强制signle-core执行来缓解这些冲突,因为我认为我的2-4核心testing系统的并发调度是我的错误的原因。

 #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define MAX_THR 12 #define NEW_THR 2 int wait_time = 0; // log global wait time int num_threads = 0; // how many threads there are pthread_t threads[MAX_THR]; // global array to collect threads pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; // sync struct thread_data { int nr; // nr of thread, serves as id int time; // wait time from rand() }; struct thread_data thread_data_array[MAX_THR+1]; void *PrintHello(void *threadarg) { if(num_threads < MAX_THR){ // using the argument pthread_mutex_lock(&mut); struct thread_data *my_data; my_data = (struct thread_data *) threadarg; // updates my_data->nr = num_threads; my_data->time= rand() % 10 + 1; printf("Hello World! It's me, thread #%d and sleep time is %d!\n", my_data->nr, my_data->time); pthread_mutex_unlock(&mut); // counter long t = 0; for(t = 0; t < NEW_THR; t++){ pthread_mutex_lock(&mut); num_threads++; wait_time += my_data->time; pthread_mutex_unlock(&mut); pthread_create(&threads[num_threads], NULL, PrintHello, &thread_data_array[num_threads]); sleep(1); } printf("Bye from %d thread\n", my_data->nr); pthread_exit(NULL); } return 0; } int main (int argc, char *argv[]) { long t = 0; // srand(time(NULL)); if(num_threads < MAX_THR){ for(t = 0; t < NEW_THR; t++){ // -> 2 threads entry point pthread_mutex_lock(&mut); // rand time thread_data_array[num_threads].time = rand() % 10 + 1; // update global wait time variable wait_time += thread_data_array[num_threads].time; num_threads++; pthread_mutex_unlock(&mut); pthread_create(&threads[num_threads], NULL, PrintHello, &thread_data_array[num_threads]); pthread_mutex_lock(&mut); printf("In main: creating initial thread #%ld\n", t); pthread_mutex_unlock(&mut); } } for(t = 0; t < MAX_THR; t++){ pthread_join(threads[t], NULL); } printf("Bye from program, wait was %d\n", wait_time); pthread_exit(NULL); } 

我希望代码不是太糟糕。 我在相当长的一段时间里没有做太多的C。 :) 问题是:

 printf("Bye from %d thread\n", my_data->nr); 

my_data-> nr有时会parsing高整数值:

 In main: creating initial thread #0 Hello World! It's me, thread #2 and sleep time is 8! In main: creating initial thread #1 [...] Hello World! It's me, thread #11 and sleep time is 8! Bye from 9 thread Bye from 5 thread Bye from -1376900240 thread [...] 

我现在没有更多的方法来configuration和debugging。 如果我debugging这个,它有效 – 有时。 有时它不会:(

感谢您阅读这个长长的问题。 :)我希望我没有太多分享我目前无法解决的困惑。

由于这个程序似乎只是一个使用线程的练习,而没有实际的目标,所以很难建议如何治疗你的问题而不是治疗症状。 我相信实际上可以将一个进程或线程固定在Linux中的一个处理器上,但是对于所有线程来说,这样做可以消除使用线程的大部分好处,而我实际上并不记得该怎么做。 相反,我会谈谈你的程序有些问题。

C编译器在进行优化时经常会做出很多假设。 其中一个假设是,除非正在检查的当前代码看起来可能会改变变量不会改变的变量(这是一个非常粗略的近似值,更准确的解释将需要很长时间)。

在这个程序中,你有不同的线程共享和改变的变量。 如果一个变量只能被线程读取(在创建线程之后是const或者const),那么你就不用担心了(在“由线程读取”中,我包含了主要的原始线程)因为如果编译器只生成一次代码读取该变量的代码(在本地临时变量中记住它),或者生成的代码一遍又一遍地读取,则该变量不会改变,因此基于它总是出来一样。

要强制编译器不这样做,你可以使用volatile关键字。 它和const关键字一样被附加到变量声明中,并告诉编译器该变量的值可以在任何时候改变,所以每次需要时都要重新读取它,并且每次为它分配一个新的值时重写它。

注意,对于pthread_mutex_t (和类似的)变量,你不需要volatile 。 如果在您的系统上构成pthread_mutex_t的类型(如果需要的话)在pthread_mutex_t的定义内使用volatile 。 另外,访问这个类型的函数会把它的地址写出来,并且专门编写来做正确的事情。

我确定现在你正在想着你知道如何修复你的程序,但并不那么简单。 你正在做一个共享变量的数学。 使用如下代码对变量进行数学运算:

 x = x + 1; 

要求您知道生成新值的旧值。 如果x是全局的,则必须在概念上将 x 加载到寄存器中, 1加到该寄存器,然后将该值x 。 在一个RISC处理器上,你实际上必须完成所有这三条指令,而且是三条指令,我相信你可以看到另外一个线程几乎同时访问同一个变量,最终可能会在x之后存储一个新的值已经阅读了我们的价值 – 使我们的价值变老,所以我们的计算和我们存储的价值将是错误的。

如果你知道任何x86程序集,那么你可能知道它有指令可以对RAM中的值进行数学运算(在一条指令中,结果都存储在RAM中的相同位置)。 你可能会认为这个指令可以用于x86系统上的这个操作,而且你几乎是对的。 问题是这条指令仍然在执行RISC指令的步骤中执行,而另一个处理器在我们做数学计算的同时还有几个机会改变这个变量。 为了避免这种情况在x86上有一个lock前缀,可以应用于一些x86指令,我相信glibc头文件包括原子宏函数在可以支持它的体系结构上执行此操作,但是这不能在所有架构。

要在所有架构上工作,您将需要:

  int local_thread_count; int create_a_thread; pthread_mutex_lock(&count_lock); local_thread_count = num_threads; if (local_thread_count < MAX_THR) { num_threads = local_thread_count + 1; pthread_mutex_unlock(&count_lock); thread_data_array[local_thread_count].nr = local_thread_count; /* moved this into the creator * since getting it in the * child will likely get the * wrong value. */ pthread_create(&threads[local_thread_count], NULL, PrintHello, &thread_data_array[local_thread_count]); } else { pthread_mutex_unlock(&count_lock); } 

现在,由于您将num_threads更改为volatile您可以在所有线程中自动测试并增加线程数。 在这个local_thread_count应该可以用作线程数组的索引。 请注意,我没有在这个代码中创建一个线程,而你的应该创建几个。 我这样做是为了让这个例子更加清晰,但是改变它并不是很难,并且将NEW_THR添加到num_threads ,但是如果NEW_THR是2并且MAX_THR - num_threads是1(不知何故),那么你必须正确处理不知何故。

现在,所有这些说,可能有另一种方式来使用信号量来完成类似的事情。 信号量就像互斥体,但是它们有一个相关的计数。 你不会得到一个值作为线程数组的索引(函数读取信号计数不会真的给你这个),但我认为它值得提及,因为它是非常相似的。

 man 3 semaphore.h 

会告诉你一点点关于它。

num_threads应该至少被标记为volatile ,并且最好是标记为atomic(尽管我相信int实际上是好的),所以至少有不同的线程看到相同的值的可能性更大。 您可能想要查看汇编程序输出以查看num_thread写入内存的时间实际上是否正在发生。

https://computing.llnl.gov/tutorials/pthreads/#PassingArguments

这似乎是问题。 你需要malloc thread_data结构。