无法理解以下程序中的pthread_create()行为?

时间:2017-05-31 05:54:29

标签: c linux unix pthreads

#include <stdio.h>
#include <pthread.h>

void *thread_func(void *arg)
{
        printf("hello, world \n");
        return 0;
}

int main(void)
{
        pthread_t t1, t2;

        pthread_create(&t1, NULL, thread_func, NULL);
        pthread_create(&t2, NULL, thread_func, NULL);

        printf("t1 = %d\n",t1);
        printf("t2 = %d\n",t2);

        return 0;
}

上面的程序创建了两个线程,每个线程打印&#34; Hello World&#34;。

所以,根据我的理解,&#34; Hello world&#34;应该打印最多2次。

但是,多次执行同一个程序(背对背)时,有些情况会出现&#34; Hello world&#34;正在打印更多而不是 2 次。所以我不清楚如何打印出意外的次数?

以下是示例输出:

[rr@ar ~]$ ./a.out
t1 = 1290651392
t2 = 1282258688
hello, world
hello, world


[rr@ar ~]$ ./a.out
t1 = 1530119936
t2 = 1521727232
hello, world
hello, world
hello, world

如上所示,在多次执行程序后,&#34;你好,世界&#34; 打印 3 次。任何人都可以建议如何打印3次?

3 个答案:

答案 0 :(得分:3)

您遇到了线程安全问题。我在Linux 16.04中运行了多次代码,它产生了许多不同的输出,而3 hello world消息的输出很少存在。更频繁地,根本没有输出,这意味着main比能够完成输出的线程更快地终止。有时会产生部分输出,如:

t1=xxxx
t2=yyyy
he

这意味着main正在退出,而只有一个线程能够在stdout缓冲区中推送一些字符。请记住,main的正常返回相当于调用exit来刷新stdio缓冲区。

虽然我无法真正理解当你观察3条消息时幕后发生的事情,但我怀疑存在一个运行竞赛让main刷新当前正被其中一个线程冲洗的缓冲区。如果不仔细检查printf的源代码,就很难说更多。一个可能的(粗略)场景看起来像:

  1. thread1填充缓冲区并进入刷新但在其开头被抢占
  2. 主要出口,因此进入冲洗并终止它并在其最后被抢占从而产生hello world
  3. thread1完成其刷新生成hello world
  4. thread2生成hello world
  5. main获取CPU并终止进程。
  6. printf未被定义为线程安全的,这意味着实现者可能会这样或不实现它(可能在大多数情况下不是这样)。因此,与使用某些共享资源的任何函数一样,您需要一些互斥锁以防止缓冲区并发等。

    在你的情况下,应该通过连接main中的线程来粗略地解决这个问题(3个输出),这将阻止线程终止前main退出/刷新。但请注意,这不会解决其他并发问题(两个线程访问相同的缓冲区......)。

答案 1 :(得分:1)

好吧,让我展示一下这种情况会发生的情况。您可能知道(如果不这样,请阅读适当的手册页)printf()是标准库不是线程安全的函数之一(pthread_<something>中有一个列表,你可能也知道printf(3)之前将其数据存储在一个缓冲区中,以发出write(2)系统调用来实际将数据写入stdout。

  1. 线程A(我总是选择不同的标签,使两个线程无法区分,因此线程A可以是线程)进行printf()调用,将缓慢的"Hello, world\n"消息放入,并准备write(2),因为终端是 tty 设备而\n结束输出字符串。
  2. 线程B获取控制权并再次调用相同的printf(2)数据(并使用"Hello, world\n"的第二个副本填充相同的缓冲区),出于同样的原因,准备并完全执行{ {1}}整个缓冲区的syscall(现在包含两条消息)刷新缓冲区。这会使write(2)出现两次。
  3. 线程A,已通过第一个"Hello, world\n"系统调用阻塞(两个线程无法同时对同一个inode进行write(2)次调用 - 这是系统内核所保证的)刷新它的视图(可能存储在其堆栈中,并且仅包含对第一条消息的引用)缓冲区(在第一个write(2)的末尾完成)并进行另一次写入还有一个"Hello, world\n"消息)
  4. 最终得分:终端上有三条"Hellow, world\n"条消息。

    最可能的事情是很难发生三条消息,因为你需要其中一个线程在时间内绕过另一条线程"Hellow, world\n"决定是否需要在填充后刷新缓冲区(是一个很短的时间)然后首先进入阻塞printf调用(如前所述,两个线程不能同时参与write(2)调用同一个文件,这是不允许的内核)

答案 2 :(得分:0)

当主程序终止时,子线程也会终止。

可能会发生两个子线程在主任务完成之前执行。在这种情况下,您会看到两个“hello worlds”和问题中显示的输出。

也可能发生主程序在一个或两个线程打印输出之前完成。在这种情况下,您可以看到一个或没有“hello world”。

我认为这个程序的单次运行不会打印3次。我想你是在一个循环中执行程序,两个运行的输出混合在一起。添加:例如,想象一下以下场景:RUN1:打印两个数字,然后调度子线程并且每个打印一个“hello world”,然后RUN1 main被安排回来并且程序结束。接下来,RUN2启动。在这种情况下,两个子线程都是在主程序打印数字之前安排的。

所以你看到的输出如下:

t1=346236763               (RUN1 - main)
t2=876237623               (RUN1 - main)
hello, world               (RUN1 - subthread)
hello, world               (RUN1 - subthread)
hello, world               (RUN2 - subthread)
hello, world               (RUN2 - subthread)
t1=3786768623              (RUN2 - main)
t2=7843473478              (RUN2 - main)

输出可能被错误地解释为好像一次运行写了4个“hello worlds”。