【问题标题】:Can threads write to different elements of same array of structures without locking?线程可以在不锁定的情况下写入相同结构数组的不同元素吗?
【发布时间】:2011-03-15 18:37:31
【问题描述】:

我正在尝试在 GCC C 应用程序中使用线程(第一次!),该应用程序在非线程模式下运行良好。当我运行它时,一些线程给出的结果全为零而不是所需的答案(我知道这是为了检查目的),但是每次运行它时给出零的线程都不相同。给出非零答案的答案是正确的,因此代码似乎可以正常运行。我想知道是否有人可以指出我可能有一些不是线程安全的地方。

我自己的想法是这可能是由于我如何收集结果或内存分配 - 我使用 malloc 和 free 但在 StackOverflow 的其他地方我看到如果与 -lpthread 链接 GCC malloc 被认为是线程安全的(我正在做)。没有使用全局/静态变量 - 一切都作为函数参数传递。

为了将结果传回主程序,我的线程例程使用了一个结构数组。每个线程都写入该数组的不同元素,因此它们不会尝试写入相同的内存。也许我在写入结果时需要使用某种形式的锁定,即使它们没有转到结构数组的相同元素?

我在这里遵循了线程代码的配方: https://computing.llnl.gov/tutorials/pthreads/#Abstract

我附上(简化的)代码摘录,以防提供任何线索(我可能错误地省略/修改了某些内容,但我不要求任何人发现错误,只是一般方法)。

typedef struct p_struct { /* used for communicating results back to main */
    int given[CELLS];
    int type;
    int status;
    /*... etc */
} puzstru;

typedef struct params_struct { /* used for calling generate function using threads */
    long seed;
    char *text;
    puzzle *puzzp;
    bool unique;
    int required;
} paramstru;
/* ========================================================================================== */
void *myfunc(void *spv) /* calling routine for use by threads */
{
    paramstru *sp=(paramstru *)spv;
    generate(sp->seed, sp->text, sp->puzzp, sp->unique, sp->required);
    pthread_exit((void*) spv);
}
/* ========================================================================================== */
int generate(long seed, char *text, puzstru *puzzp, bool unique, int required)
{
/* working code , also uses malloc and free,
    puts results in the element of a structure array pointed to by "puzzp", 
    which is different for each thread
    (see calling routine below :        params->puzzp=puz+thr; )
    extract as follows: */
            puzzp->given[ix]=calcgiven[ix];
            puzzp->type=1; 
            puzzp->status=1;
            /* ... etc */
}
/* ========================================================================================== */


int main(int argc, char* argv[])
{
    pthread_t thread[NUM_THREADS];
    pthread_attr_t threadattr;
    int thr,threadretcode;
    void *threadstatus;
    paramstru params[1];

    /* ....... ETC */

/* set up params structure for function calling parameters */
    params->text=mytext;
    params->unique=TRUE;
    params->required=1;

    /* Initialize and set thread detached attribute */
    pthread_attr_init(&threadattr);
    pthread_attr_setdetachstate(&threadattr, PTHREAD_CREATE_JOINABLE);

    for(thr=0; thr<NUM_THREADS; thr++)
    {
        printf("Main: creating thread %d\n", thr);
        params->seed=ran_arr_next(startingseeds); 
        params->puzzp=puz+thr;
        threadretcode = pthread_create(&thread[thr], &threadattr, myfunc, (void *)params); 
        if (threadretcode)
        {
            printf("ERROR; return code from pthread_create() is %d\n", threadretcode);
            exit(-1);
        }
    }

    /* Free thread attribute and wait for the other threads */
    pthread_attr_destroy(&threadattr);
    for(thr=0; thr<NUM_THREADS; thr++)
    {
        threadretcode = pthread_join(thread[thr], &threadstatus);
        if (threadretcode)
        {
            printf("ERROR; return code from pthread_join() is %d\n", threadretcode);
            exit(-1);
        }
        printf("Main: completed join with thread %d having a status of %ld\n",thr,(long)threadstatus);
    }

/* non-threaded code, print results etc ............. */

    free(startingseeds);
    free(puz);
    printf("Main: program completed. Exiting.\n");
    pthread_exit(NULL);
}

为了其他阅读本文的人的利益-所有答案都是正确的,并且标题中问题的答案是肯定的,线程可以安全地写入同一结构数组的不同元素,我的问题出在调用例程中- 以下是修改后的代码 sn-p(现在可以正常工作):

    paramstru params[NUM_THREADS];

    for(thr=0; thr<NUM_THREADS; thr++)
    {
        printf("Main: creating thread %d\n", thr);
    /* set up params structure for function calling parameters */
        params[thr].text=mytext;
        params[thr].unique=TRUE;
        params[thr].required=1;
        params[thr].seed=ran_arr_next(startingseeds); 
        params[thr].puzzp=puz+thr;
        threadretcode = pthread_create(&thread[thr], &threadattr, myfunc, (void *)&params[thr]); 
        if (threadretcode)
        {
            printf("ERROR; return code from pthread_create() is %d\n", threadretcode);
            exit(-1);
        }
    }

【问题讨论】:

  • 嗯。变量 puz 在哪里声明?是puzzle * 类型吗? ix 是如何计算的?我猜您的问题出在您标记为“工作代码”的块中。 ;)

标签: c multithreading gcc thread-safety


【解决方案1】:

要回答您的问题,从不同线程写入同一数组的不同元素而无需锁定是完全可以的。如果两个线程在没有同步(例如锁定)的情况下写入同一个 byte,则只会有 data race

正如其他答案所指出的那样,您编写的代码中断的原因是因为您将指向同一 params 对象的指针传递给每个线程,然后您修改了该对象。您可能想为每个线程创建一个新的param

【讨论】:

  • 虽然安全,但如果不小心操作,可能会导致性能不佳。如果多个线程不断访问同一缓存行上的数组元素,则会有大量的缓存行弹跳,这很昂贵。
  • 感谢您提供有关不同字节的信息。我找错地方了。
  • 你确定它是安全的吗?对于不能执行小于字写入的机器,写入一个字节是一个读-修改-写操作的机器呢?我同意这样的机器是病态的废话,不应该用于任何现实世界的目的,但严格来说,我相信如果你声称完全可移植,就需要考虑它们......
  • @R:你说得对,最小的原子内存单元确实取决于架构。
  • @ninjalj 你怎么能小心这个?
【解决方案2】:
paramstru params[1];

代码将相同的结构传递给所有线程。只是线程初始化循环正在覆盖线程应该处理的数据:

for(thr=0; thr<NUM_THREADS; thr++)
    {
        printf("Main: creating thread %d\n", thr);
        params->seed=ran_arr_next(startingseeds); /* OVERWRITE  */
        params->puzzp=puz+thr; /* OVERWRITE  */

非线程代码起作用的原因是因为每次对myfunc() 的调用都会在params 结构更改之前终止。

【讨论】:

  • 谢谢,我猜在读取参数的线程和设置循环的下一次迭代之间存在竞争。
  • 您可以使用屏障(稍微慢)或为每个线程使用单独的结构(稍微浪费内存)来解决这个竞争。
【解决方案3】:

您只创建了参数结构的一个副本,并且正在覆盖它并将相同的地址传递给每个线程。你不想paramstru params[NUM_THREADS];吗?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-23
    • 1970-01-01
    • 2021-03-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多