【问题标题】:"thread fork" in C (ideally POSIX, but just Linux works)C 中的“线程分叉”(理想情况下是 POSIX,但只有 Linux 有效)
【发布时间】:2011-09-08 00:03:07
【问题描述】:

是否有任何库/pthread 包装器/clone 参数可以让我拥有tfork——就像fork() 一样,允许您在上下文中继续代码执行,而不是指向在新线程下执行的新函数。

如果没有,有什么简单的方法可以自己编写吗?


理想情况下,用法与 fork 类似,但含义是线程状的,因此举一个人为的例子:

int main() {
        int ival = 0;
        if(tfork() == 0) {
                sleep(10);
                ival = 5;
                _exit(); // or exit or return or whatever
        } else {
                while(1) {
                        printf("ival=%d\n", ival);
                        if(ival != 0) {
                                printf("ival changed. done.\n");
                                return 0;
                        }
                        sleep(1);
                }
        }
}

应该输出:

ival=0
ival=0
ival=0
ival=0
ival=0
ival=0
ival=0
ival=0
ival=0
ival=0
ival=5
ival changed. done.

【问题讨论】:

  • AFAIK 事件 fork() 不允许分叉进程在分叉函数的上下文中执行,因为
  • 我认为这对于任何非平凡的使用都会有问题——使用同一个堆栈的两个线程会很快相互连接。想象一下原来的线程调用一个函数,而新线程调用一个不同的函数,并且两个线程几乎同时试图将它们的函数的局部变量推送到同一个堆栈上……某人的局部变量会被破坏并崩溃。跨度>
  • tfork 可以将堆栈作为父堆栈的副本,​​不是吗?
  • @Jeremy Friesner:同一个堆栈上的两个线程显然是灾难性的,但分配第二个堆栈并复制状态会起作用。
  • 它必须重新定位堆栈上指向堆栈上其他位置的任何指针。

标签: c linux multithreading unix posix


【解决方案1】:

在 Linux 中,fork() 和 pthread_create() 只是对 clone() 的包装。您自己直接调用 call clone() 以获得所需的效果。例如(不是实际代码,但非常接近):

pid = syscall(SYS_clone, (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM | CLONE_PARENT), NULL, NULL, NULL );

if(-1 == pid) { 
    return -1;
}

if(pid) {
          return pid;
    } else {

     // Your new thread code goes here
   }

更多详情:http://linux.die.net/man/2/clone

【讨论】:

  • 我查看了手册页中的实际代码,但无法弄清楚。你能详细说明一下吗?
  • 当您调用 pthread_create 时,您会调用 pthread 库中的一个库函数,该函数使用 Linux 系统调用 clone(2) 来创建新线程。 pthread_create() 是根据 POSIX 编写的,它规定新线程在其自己的函数中开始。但是,clone 是 Linux 特定的,没有这样的限制,所以您可以做的是使用 clone 构建您自己的 pthread_create 版本,而不仅仅是根据需要创建一个新函数。上面的代码是伪代码(因为缺少一些标志),显示了编写您自己的 pthread_create 的一般方法。
  • 请注意 clone(2) 手册页中的这一段:sys_clone 的另一个区别是 child_stack 参数可能为零,在这种情况下,写时复制语义确保子节点获得单独的副本当任一进程修改堆栈时的堆栈页数。在这种情况下,为了正确操作,不应指定 CLONE_VM 选项。
  • 这意味着要正确操作,您必须设置一个新堆栈,或者取消共享整个地址空间。
【解决方案2】:

您不能这样做,因为线程共享相同的地址空间。从相同的执行上下文继续(就像 fork() 一样)意味着它们的堆栈在相同的内存中。

当然,两个线程需要有自己的堆栈,否则会出问题。

这就是为什么你需要指定一个函数来启动一个新线程——因为新线程有一个新堆栈。

【讨论】:

  • 这在技术意义上是正确的,但我认为线程实现没有理由必须公开那么多底层实现。请参阅 OpenMP 作为证明。
【解决方案3】:

嗯,这几乎是vfork() 在 Linux 上所做的,它共享父级的内存(但不共享线程应该共享的其余内容)。

由于共享父级的内存会导致您的问题的 cmets 中讨论过的问题(snd 一些其他问题),因此 vfork() 父级实际上已暂停,直到子级删除对该内存的所有引用,通常通过execve()_exit() (注意下划线,exit()vfork() 孩子不安全)。

vfork() 孩子可以保证唯一安全地做的事情是调用exec*()_exit()。有了很多知识和黑巫术,有些人可以在 Linux 上对vfork() 孩子做更多的事情,但这涉及到对细节的大量关注。

所以,正如你所看到的,在共享内存和返回 la fork() 时,你可以做的事情有很多限制:线程创建通常是通过调用来完成的,这是有充分理由的一个函数

【讨论】:

  • 在我提出的情况下,如果您可以正确复制/修复堆栈,为什么会很难做其他事情?
  • @aharon:当前函数需要访问现有堆栈。我看到了两种可能性:与我概述的问题以及其他一些问题共享;取消共享堆栈,您需要修改 clone() 系统调用以允许仅取消共享部分 VMA。
  • 复制/修复堆栈不可能。相当于停机问题。
【解决方案4】:

你可以用openMP做这样的事情

不完全是这样,但它确实会自动创建线程,并且有同步和通信机制。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-18
    • 1970-01-01
    • 1970-01-01
    • 2017-08-09
    • 1970-01-01
    • 1970-01-01
    • 2015-10-08
    相关资源
    最近更新 更多