【问题标题】:Linux - force single-core execution and debug multi-threading with pthreadLinux - 强制单核执行并使用 pthread 调试多线程
【发布时间】:2010-11-22 14:40:54
【问题描述】:

我正在调试 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->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
[...] 

我现在没有更多的方法来分析和调试这个。 如果我调试它,它会起作用 - 有时。有时它不会:(

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

【问题讨论】:

  • 在这种情况下,我很抱歉我手头没有好的、基本的多线程教程——也许其他一些人可以指出一个好的?很抱歉,但目前我没有时间自己回答这个问题......
  • 它可能不会产生您想要的结果,但您可以使用“taskset”来设置正在运行(或启动)进程的亲和性...
  • 我想知道您的 threads 数组是否溢出到您的 num_threads 变量中。

标签: c linux multithreading pthreads


【解决方案1】:

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

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

在这个程序中,您可以使用不同线程共享和更改的变量。如果变量仅由线程读取(const 或在创建查看它的线程之后有效地 const),那么您不必担心(并且在“线程读取”中,我包括 main original 线程),因为如果编译器只生成代码来读取该变量一次(在本地临时变量中记住它)或者如果它生成代码来一遍又一遍地读取它,变量不会改变总是相同的,因此基于它的计算结果总是相同的。

要强制编译器不这样做,您可以使用volatile 关键字。它像const 关键字一样附加在变量声明中,并告诉编译器该变量的值可以随时更改,因此每次需要它的值时重新读取它,并在每次为其提供新值时重写它已分配。

请注意,对于pthread_mutex_t(和类似的)变量,您不需要volatile。如果您的系统上构成pthread_mutex_t 的类型需要它,volatile 将在pthread_mutex_t 的定义中使用。此外,访问这种类型的函数会获取它的地址,并且是专门为做正确的事情而编写的。

我确定您现在认为您知道如何修复您的程序,但这并不是那么简单。您正在对共享变量进行数学运算。使用如下代码对变量进行数学运算:

x = x + 1;

要求您知道旧值才能生成新值。如果x 是全局的,那么您必须在概念上加载 x 到寄存器中,添加 1 到该寄存器,然后存储值回到x。在 RISC 处理器上,您实际上必须执行所有 3 条指令,并且作为 3 条指令,我相信您可以看到另一个线程几乎同时访问同一个变量可能最终存储一个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_THR 添加到num_threads 应该不会太难,但是如果NEW_THR 是2 而MAX_THR - num_threads 是1(不知何故)然后你必须以某种方式正确处理。

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

man 3 semaphore.h

会告诉你一点。

【讨论】:

  • num_threads 不需要是 volatile,只要它始终在保持 count_lock 的情况下访问即可。 pthread_mutex_lock()pthread_mutex_unlock() 必然充当编译器屏障。
  • 但是只要num_threads 的大小可以被原子访问(例如sig_atomic_t),您就可以在没有锁的情况下(重复地)进行只读访问而无需获取锁。如果在锁定和解锁之间重复提及num_threads,此处说volatile 只会影响输出代码。在我的代码中,我曾将它作为 R 值提到一次,然后作为 L 值提到一次。
【解决方案2】:

num_threads 至少应该标记为volatile,最好也标记为原子(尽管我相信int 实际上很好),这样至少不同线程看到的可能性更高相同的值。您可能想查看汇编程序的输出,以了解何时将 num_thread 写入内存。

【讨论】:

  • 另一个大问题是创建的线程可能多于MAX_THR,这将对threads数组进行越界访问,将程序抛入未定义的行为...
  • volatileatomic 都不是解决代码问题的必要条件。
  • 我同意它的不足,但为什么没有必要?
  • volatile 不适用于多线程程序,你真的不应该那样使用它,永远。
  • mjmwired.net/kernel/Documentation/… 挥发性物质被认为是有害的
【解决方案3】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-25
    • 1970-01-01
    • 2016-03-06
    • 1970-01-01
    • 2021-12-24
    • 1970-01-01
    相关资源
    最近更新 更多