Linux - 使用pthread强制执行单核执行和调试多线程

时间:2010-11-22 14:40:55

标签: c linux multithreading pthreads

我正在调试C,pthread和Linux的多线程问题。在我的MacOS 10.5.8上,C2D运行正常,在我的Linux计算机(2-4核)上产生了不需要的输出。

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

我描述了程序执行,使用Valgrind进行检测:

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

我在BSS段中遇到了一些冲突 - 这是由我的全局结构和线程计数器变量引起的。但是我可以通过强制签名核心执行来缓解这些冲突,因为我认为2-4核心测试系统的并发调度是我的错误的原因。

#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-&gt; nr有时会解析高整数值:

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
[...] 

我现在没有更多方法来分析和调试它。 如果我调试它,它有效 - 有时。有时不会:(

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

3 个答案:

答案 0 :(得分:3)

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

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

在这个程序中,您拥有由不同线程共享和更改的变量。如果一个变量只被线程读取(const或者在查看它的线程之后有效的const),那么你就不必担心了(在“线程读取”中我包括了main 原始线程)因为如果编译器只生成一次读取该变量的代码(在本地临时变量中记住它)或者它生成的代码一遍又一遍地读取它,变量就不会改变值总是相同的,因此基于它的计算总是相同的。

要强制编译器不这样做,您可以使用volatile关键字。它与const关键字一样附加到变量声明,并告诉编译器该变量的值可以在任何时刻改变,因此每次需要它时重新读取它,并在每次新值时重写它因为它被分配了。

请注意,对于pthread_mutex_t(和类似)变量,您不需要volatile。如果在系统pthread_mutex_t上组成volatile的类型需要,则会在pthread_mutex_t的定义中使用。此外,访问此类型的函数会获取它的地址,并且专门编写以执行正确的操作。

我相信你现在认为你知道如何修复你的程序,但事情并非那么简单。你正在对共享变量进行数学运算。使用以下代码对变量进行数学运算:

x = x + 1;

要求您知道旧值以生成新值。如果x是全局的,那么您必须在概念上加载 x到一个寄存器中, 1添加到该寄存器,然后 store < / em>该值返回x。在RISC处理器上,你实际上必须完成所有这3条指令,并且是3条指令我相信你可以看到另一个线程几乎同时访问同一个变量的结果存储 a在我们读取了我们的价值之后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的末尾应该可以用作线程数组的索引。请注意,我没有在此代码中创建1个线程,而您的应用程序创建了几个。我这样做是为了让示例更加清晰,但是将其更改为NEW_THRnum_threads并不应该太难,但如果NEW_THR为2且{{1}是1(不知何故)然后你必须以某种方式正确处理。

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

MAX_THR - num_threads

会告诉你一些事情。

答案 1 :(得分:1)

num_threads至少应该标记为volatile,并且最好也标记为原子(虽然我相信int几乎没有问题),所以至少有更高的机会不同的线程看到相同的值。您可能希望查看汇编程序输出,以查看实际上是否发生num_thread到内存的写入。

答案 2 :(得分:0)

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

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