【问题标题】:pthread: one printf statement get printed twice in child threadpthread:一个 printf 语句在子线程中打印两次
【发布时间】:2012-11-13 02:13:04
【问题描述】:

这是我的第一个 pthread 程序,我不知道为什么 printf 语句会在子线程中打印两次:

int x = 1;

void *func(void *p)
{
    x = x + 1;
    printf("tid %ld: x is %d\n", pthread_self(), x);
    return NULL;
}

int main(void)
{
    pthread_t tid;
    pthread_create(&tid, NULL, func, NULL);

    printf("main thread: %ld\n", pthread_self());

    func(NULL);
}

在我的平台上观察到的输出(Linux 3.2.0-32-generic #51-Ubuntu SMP x86_64 GNU/Linux):

1.
main thread: 140144423188224
tid 140144423188224: x is 2

2.
main thread: 140144423188224
tid 140144423188224: x is 3

3.
main thread: 139716926285568
tid 139716926285568: x is 2
tid 139716918028032: x is 3
tid 139716918028032: x is 3

4.
main thread: 139923881056000
tid 139923881056000: x is 3
tid 139923872798464tid 139923872798464: x is 2

对于3,来自子线程的两条输出线

对于4,和3一样,连输出都是交错的。

【问题讨论】:

  • 在多线程应用程序中,您需要担心多线程使用的资源,在您的示例中,屏幕输出是多线程使用的资源,因此需要使用互斥锁来处理。
  • @tAmirNaghizadeh:在 POSIX(和 Linux)上,使用 FILE* 的函数被指定为锁定对象(少数 I/O 函数专门不锁定并将“解锁”作为他们名字的一部分)。
  • 我知道我在上个月左右看到了另一个关于 SO 的问题,关于当线程未加入并且进程结束时额外的输出行,但我现在找不到它(也许它已被删除?)。一个从来没有得到满意回答的老的:stackoverflow.com/questions/10322175我希望能就此给出一个体面的答案。
  • @tAmirNaghizadeh:我认为当进程结束而不加入线程时,有时会导致线程输出重复的问题相同。我认为这是 glibc 中的一个问题,但我愿意有人指出为什么系统可以这样做(即指出未定义的行为在哪里)。我认为printf() 相对于进程中的其他printf() 调用(实际上,任何使用stdout 的POSIX 函数)应该是原子的论点在这个答案和cmets 中:stackoverflow.com/a/13190750/12711
  • 对此的更新:这个问题有一个针对 glibc 的开放错误报告:sourceware.org/bugzilla/show_bug.cgi?id=14697

标签: pthreads printf


【解决方案1】:

看起来真正的答案是 Michael Burr 的评论,它引用了这个 glibc 错误:https://sourceware.org/bugzilla/show_bug.cgi?id=14697

总而言之,glibc 在程序退出期间没有正确处理 stdio 缓冲区。

【讨论】:

    【解决方案2】:

    嗯。您的示例使用来自不同线程的相同“资源”。一种资源是变量 x,另一种是标准输出文件。所以你应该使用如下所示的互斥锁。最后还有一个 pthread_join 等待另一个线程完成它的工作。 (通常检查所有这些 pthread... 调用的返回码也是一个好主意)

    #include <pthread.h>
    #include <stdio.h>
    int x = 1;
    pthread_mutex_t mutex;
    
    void *func(void *p)
    {
        pthread_mutex_lock (&mutex);
        x = x + 1;
        printf("tid %ld: x is %d\n", pthread_self(), x);
        pthread_mutex_unlock (&mutex);
        return NULL;
    }
    
    int main(void)
    {
        pthread_mutex_init(&mutex, 0);
    
        pthread_t tid;
        pthread_create(&tid, NULL, func, NULL);
    
        pthread_mutex_lock (&mutex);
        printf("main thread:  %ld\n", pthread_self());
        pthread_mutex_unlock (&mutex);
    
        func(NULL);
        pthread_join (tid, 0);
    }
    

    【讨论】:

    • 好的,这个答案部分错误,这里有几个问题可以解决这个问题,例如stackoverflow.com/questions/467938/…。使用代码,并在除 x 递增的地方之外的所有地方都删除了互斥锁。仍然工作始终正确。但是当 pthread_join 被删除时,事情就开始变得奇怪了。我对这种行为的解释是,如果没有连接,主程序会退出,并且图像会启动,这可能涉及删除 glibc 中的结构(互斥体?),这会使 printf 不再正常运行。
    【解决方案3】:

    线程通常通过时分复用发生。处理器在两个线程之间均匀切换通常效率低下,因为这需要更多的努力和更高的上下文切换。通常,您会发现一个线程在切换之前会执行多次(如示例 3 和 4 的情况。子线程在最终终止之前执行了多次(因为主线程已退出)。

    示例2:不知道为什么x在没有输出的情况下被子线程增加了。

    考虑一下。主线程执行。它调用 pthread 并创建一个新线程。新的子线程递增 x。在子线程能够完成 printf 语句之前,主线程开始执行。突然之间,它也增加了 x。然而,主线程也能够运行 printf 语句。突然 x 现在等于 3。 主线程现在终止(也导致子 3 退出)。 这很可能是您的案例 2 中发生的情况。

    示例 3 清楚地表明变量 x 已由于低效锁定和堆栈数据损坏而损坏!!

    有关线程是什么的更多信息。

    Link 1 - Additional info about threading

    Link 2 - Additional info about threading

    您还会发现,因为您使用的是 x 的全局变量,所以对这个变量的访问是在线程之间共享的。这很糟糕.. 非常非常糟糕,因为访问同一变量的线程会创建竞争条件和数据损坏,因为变量 x 的同一寄存器上发生了多次读写。 正是出于这个原因,使用了互斥锁,它本质上是在更新变量时创建一个锁,以防止多个线程同时尝试修改同一个变量。 互斥锁将确保 x 按顺序更新,而不是像您的情况那样偶尔更新。

    有关 Pthreads in General 和 Mutex 锁定示例的更多信息,请参阅此链接。
    Pthreads and Mutex variables

    干杯,
    彼得

    【讨论】:

    • @Peter H “一个线程在切换之前会执行几次(就像示例 3 和 4 的情况一样。)”,那么在我的情况下这太棘手了,子线程被执行了两次并且两者都在主线程更新 x 之后和自身更新 x 之前切换?
    • 除非加入,否则每个执行线程的生命周期都限于主线程的生命周期。如果要确保子线程完全执行,则需要使用函数 pthread_join,通过该函数它将子线程连接到主线程。见This link for examples
    • @PeterH 在链接 2 中,它明确指出当线程“从其启动例程正常返回。它的工作已完成”时终止。这与您响应的第一部分发生冲突,即一个线程在切换之前执行了几次。 (这意味着线程只是运行多次,因此多次调用printf)重复输出的可能问题是@ZanLynx引用的错误
    • @MitchLaskis 线程可以在完成工作后终止,这是正确的......但是子线程可以在主线程完成时提前终止(IE 子线程可能如果 main 先完成,则永远不会完成)。由于作者将 func 既作为子线程又作为主线程调用,因此很难分辨。完全有可能场景 3 中的额外 printf 是由 Zan 描述的 stdio 缓冲区中的错误引起的,但是变量 x 缺少锁定使得很难判断。结束故事...实施锁定/互斥锁以确保线程安全。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多