【问题标题】:Thread issue using global variable使用全局变量的线程问题
【发布时间】:2018-05-08 14:31:54
【问题描述】:

我正在研究 Linux 和操作系统中使用的线程。我正在做一个小运动。目标是对一个全局变量的值求和,最后查看结果。当我看到最终结果时,我的心就炸了。代码如下

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>

int i = 5;

void *sum(int *info);

void *sum(int *info)
{
    //int *calc = info (what happened?)
    int calc = info;

    i = i + calc;

    return NULL;
}

int main()
{
    int rc = 0,status;
    int x = 5;

    pthread_t thread;

    pthread_t tid;
    pthread_attr_t attr;

    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    rc = pthread_create(&thread, &attr, &sum, (void*)x);
    if (rc) 
    {              
        printf("ERROR; return code from pthread_create() is %d\n", rc);
        exit(-1);
    }

    rc = pthread_join(thread, (void **) &status);
    if (rc) 
    {
        printf("ERROR; return code from pthread_join() is %d\n", rc);
        exit(-1);
    }

    printf("FINAL:\nValue of i = %d\n",i);
    pthread_attr_destroy(&attr);
    pthread_exit(NULL);

    return 0;
}

如果我将变量 calc 作为 int *cal 放入 sum 函数中,则 i 的最终值为 25(不是预期值)。但是,如果我将其设为 int calc,则 i 最终值为 10(我在本练习中的预期值)。我不明白当我将变量 calc 设置为 int *calc 时,我的值怎么会是 25。

【问题讨论】:

    标签: c linux multithreading pthreads


    【解决方案1】:

    阅读一些tutorial about pthreads。在访问和修改多个threads 中的全局 数据时,您不能期望重现行为(没有与synchronization 相关的额外编码预防措施)。 AFAIU 您的代码表现出一些棘手的undefined behavior,而您应该是scared(在您的情况下,这可能只是未指定的行为)。要解释观察到的具体行为,您需要深入研究实现细节(而且您没有时间:研究生成的汇编代码、特定硬件的行为等...)。

    另外(因为info 是一个指针 指向int

    int calc = info;
    

    没有多大意义(我猜你打错了)。在某些系统上(比如我的运行 Linux 的 x86-64),指针比int 宽(所以calc 丢失了info 的一半位)。在其他(罕见的)系统上,它的尺寸可能更小。有时(运行 Linux 的 i686)它可能具有相同的大小。如果要将指针强制转换为整数值并返回,则应该考虑 &lt;stdint.h&gt; 中的 intptr_t

    实际上,您应该使用mutex 保护对该全局数据的访问(在i 内,可能通过指针访问),或者在C11 中使用atomic 操作,因为该数据由多个并发线程使用。

    所以你可以像这样声明一个全局互斥体

     pthread_mutext_t mtx = PTHREAD_MUTEX_INITIALIZER;
    

    (或使用pthread_mutex_init)然后在您的sum 中编码

    pthread_mutex_lock(&mtx);
    i = i + calc;
    pthread_mutex_unlock(&mtx);
    

    (另见pthread_mutex_lockpthread_mutex_lock(3p))。当然你也应该在你的main中编码。

    锁定一个互斥体有点昂贵(通常是加法的几十倍),即使在它被解锁的情况下也是如此。如果您可以在 C11 中编码,您可能会考虑原子操作,因为您处理的是整数。您将声明 atomic_int i; 并在其上使用 atomic_loadatomic_fetch_add

    如果您好奇,另请参阅 pthreads(7)futex(7)

    多线程编程真的很难(对所有人来说)。您不能期望行为通常是可重现的,并且您的代码显然可以按预期运行并且仍然非常错误(并且在某些不同的系统上会以不同的方式工作)。另请阅读memory modelsCPU cachecache coherenceconcurrent computing...

    考虑使用 GCC 线程清理程序 instrumentation options 和/或 valgrindhelgrind

    【讨论】:

    • 即使它看起来工作,它仍然是非常错误的。
    • 谢谢你!你帮了我很多!我再研究一下同步 错了还是没有同步?
    【解决方案2】:

    问题与线程或全局变量无关,与 C 的指针算法有关。

    您可以使用以下代码获得完全相同的结果:

    int main()
    {
        int i = 5;
        int *j = 5;
        i = i + j;
        printf("%d\n", i); // this is 25
    }
    

    这里发生的情况是,您将指针 j 分配给值 5,并将该指针“加 5”。给一个指针加 5 相当于在内存中增加了足够的空间来容纳这个指针指向的 5 个对象。在这种情况下,sizeof(int) 为 4,因此您实际上是在添加 4*5,即 20。因此,结果为 25,即 5 + 4*5 = 25。

    另一个警告,由于 sizeof(int) 取决于机器,您的结果可能会有所不同。

    让我再举一个例子来说明这一点:

    int main()
    {
        int i = 5;
        uint64_t *j = 5;
        i = i + j;
        printf("%d\n", i); // result is 45
    }
    

    因为 sizeof(uint64_t) 是 8,这相当于在原来的 5 上加上 5*8,所以结果是 5 + 5*8 = 45。

    这段代码演示了类型转换的许多问题。 “x”首先声明为“int”,转换为通用指针“void*”,然后隐式转换为“int*”,然后再转换回“int”。正如您在此处所展示的那样,这些类型的铸造肯定会在脚上开枪。

    【讨论】:

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