【问题标题】:Threads not acting on global variable线程不作用于全局变量
【发布时间】:2016-08-05 18:44:12
【问题描述】:

我有一个庞大的多线程代码库。有 100 个线程。 99 个线程有一个函数do_thread_operations 与它们中的每一个关联。第 100 个线程有一个与之关联的操作 monitor_99threads

do_thread_operations 有无限循环,所以它们永远不会停止。 monitor_99threads 跟踪时间。启动一分钟后,它必须关闭所有 99 个线程并返回主线程。

以下是我的代码。

#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>

int *to_close_thread;

void * do_thread_operations(void * thread_data){
    int i = *(int *)thread_data;
    while (1){
       //do something
       if (to_close_thread[i]) break;
    }
}

void * monitor_99threads(void * data){ 
    int i = 0;
    while(1){
       i++;
       if(i > 60){
          for (int i = 0; i < 99; ++i){
              printf("Closing %d", i);
              to_close_thread[i] = 1;  
          }
       }
    }
}

int main(){
    to_close_thread = (int *)malloc(99 * sizeof(int));
    memset(to_close_thread, 0, 99*sizeof(int));
    pthread_t threads[100];
    for (int i = 0; i < 99; ++i){
        int j = i;
        pthread_create(&threads[i], NULL, do_thread_operations, (void *)&j);
    }
    pthread_create(&threads[99], NULL, monitor_99threads, NULL);
    for (int i = 0; i < 100; ++i){
        pthread_join(threads[i], NULL);
    }

}

问题是虽然关闭了打印,但线程并没有跳出它们的循环。

【问题讨论】:

  • 您将j 传递给线程,然后更改mainj 的值。您需要一个数组id[100],然后将&amp;id[i] 传递给线程。
  • @user3386109 不,我正在重新声明它。
  • 可能是同步问题。你不能假设你的线程会按照创建的顺序读取变量 j。
  • @clarasoft-it,我明白这一点。但即便如此,线程也会在某个时候停止,对吧?它永不中断。
  • @SonuMishra 理论上,j 在块的末尾超出范围,因此不复存在。这意味着线程(理论上)正在尝试访问不存在的东西(这比仅访问其值已更改的东西更糟糕)。实际上,编译器在堆栈上为j 分配四个字节,然后为j 的每个新实例重用这四个字节。为了更好地理解这一点,您应该使用-S 进行编译并查看编译器生成的汇编代码。

标签: c multithreading pthreads


【解决方案1】:

除了@P.P. 描述的问题外,to_close_thread 的引用似乎存在同步问题。由于元素不是_Atomic,并且它们在没有互斥锁、信号量或其他同步对象保护的情况下被访问,do_thread_operations() 线程可能永远看不到control_99threads() 执行的任何写入。

另外请注意,将指向to_close_thread 基址的偏移量的指针作为线程参数传递有点愚蠢,而您可以直接将指针传递给适当的元素。那么你就不需要任何辅助变量了:

    for (int i = 0; i < 99; ++i){
        pthread_create(&threads[i], NULL, do_thread_operations,
                (void *) (to_close_thread + i));
    }

do_thread_operations 可以将其转换回int * 并直接取消引用它(在互斥锁或其他适当同步对象的保护下)。

【讨论】:

  • “线程可能永远不会看到由 control_99threads() 执行的任何写入”。我知道当线程和监视器同时访问元素时,线程可能会读取 0,而监视器已经写入 1。但是在下一次迭代中,线程会将其读取为 1 并中断。我错了吗?
  • @SonuMishra,是的,你错了。有几种方法可以查看它。其中之一是,如果没有任何同步,对于给定的写入是在给定的读取之前还是之后发生没有有意义的定义,因此工作线程可能在有限时间内看不到任何写入。一种更物理的看待它的方式是,如果没有同步,写入可能仅反映在一个内核的缓存中(或在某些情况下,仅反映在寄存器中),其中调度在其他内核上的线程在无限时间内不可见.
  • 每个线程访问to_close_thread 中的唯一元素。所以,不知道你为什么建议同步是必要的。 that it's a bit silly to pass a pointer to an offset from the base of to_close_thread as the thread argument - 实际上,to_close_thread 仅由 montior 修改并将其传递给线程,正如您所建议的那样不起作用,因为它们都是 0。即使您将其设置为 1在将它传递给线程之前,这会引入数据竞争,因为监视器可能在线程可以读取它之前写入,并且它甚至会在 montior 设置它之前使线程退出条件为真!
  • @P.P. “如果其中一个修改内存位置而另一个读取或修改相同的内存位置,则两个表达式评估 冲突”(C2011,5.1.2.4/4)。 “如果程序的执行包含不同线程中的两个冲突操作,则程序的执行包含 数据竞争,其中至少一个不是原子的,并且两者都不会在另一个之前发生。任何此类数据竞争都会导致未定义行为”(C2011,5.1.2.4/25)。 “发生在之前”是一种已定义的关系,在 O.P. 的代码或您的代码中都没有建立,但会通过同步建立。
  • @P.P.至于线程参数,我不建议传递指针的 referents,而是建议传递指针本身,正如我的代码所示。工作线程将取消引用这些指针以检查指向的值。这绝对等同于 O.P. 的代码所做的,但它摆脱了临时变量。
【解决方案2】:

这里,

  for (int i = 0; i < 99; ++i){
        int j = i;
        pthread_create(&threads[i], NULL, do_thread_operations, (void *)&j);
    }

您正在传递j 的地址,该地址具有块范围并具有自动存储持续时间。你应该是这样的:

    int arr[100] = {0}; /* adjust the number or allocate using malloc as per needs */
    for (int i = 0; i < 99; ++i){
        arr[i] = i;
        pthread_create(&threads[i], NULL, do_thread_operations, &arr[i]);
    }

另一个问题是您的监控线程在发送关闭消息后没有中断。事实上,它在无限循环中运行。当满足(无论)条件时,您可能需要一个break; 语句。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-16
    • 1970-01-01
    • 2013-07-13
    • 2017-03-07
    相关资源
    最近更新 更多