fork()Vs Thread的公平比较

时间:2010-10-14 15:43:14

标签: c++ c multithreading unix fork

我正在讨论fork()Vs thread()与任务并行化的相对成本。

我们理解进程与线程之间的基本区别

主题:

  • 易于在线程之间进行通信
  • 快速上下文切换。

过程:

  • 容错。
  • 与父母沟通不是真正的问题(打开烟斗)
  • 努力与其他子流程沟通

但是我们不同意流程Vs线程的启动成本 因此,为了测试理论,我编写了以下代码。我的问题:这是衡量启动成本的有效测试,还是我遗漏了一些东西。此外,我对每个测试在不同平台上的表现感兴趣。

fork.cpp

#include <boost/lexical_cast.hpp>
#include <vector>
#include <unistd.h>
#include <iostream>
#include <stdlib.h>
#include <time.h>

extern "C" int threadStart(void* threadData)
{
    return 0;
}

int main(int argc,char* argv[])
{
    int threadCount =  boost::lexical_cast<int>(argv[1]);

    std::vector<pid_t>   data(threadCount);
    clock_t                 start   = clock();
    for(int loop=0;loop < threadCount;++loop)
    {
        data[loop]  = fork();
        if (data[looo] == -1)
        {
            std::cout << "Abort\n";
            exit(1);
        }
        if (data[loop] == 0)
        {
            exit(threadStart(NULL));
        }
    }
    clock_t                 middle   = clock();
    for(int loop=0;loop < threadCount;++loop)
    {
        int   result;
        waitpid(data[loop], &result, 0);
    }
    clock_t                 end   = clock();

   std::cout << threadCount << "\t" << middle - start << "\t" << end - middle << "\t"<< end - start << "\n";
}

Thread.cpp

#include <boost/lexical_cast.hpp>
#include <vector>
#include <iostream>
#include <pthread.h>
#include <time.h>


extern "C" void* threadStart(void* threadData)
{
    return NULL;
}   

int main(int argc,char* argv[])
{
    int threadCount =  boost::lexical_cast<int>(argv[1]);

    std::vector<pthread_t>   data(threadCount);

    clock_t                 start   = clock();
    for(int loop=0;loop < threadCount;++loop)
    {
        if (pthread_create(&data[loop], NULL, threadStart, NULL) != 0)
        {
            std::cout << "Abort\n";
            exit(1);
        }
    }   
    clock_t                 middle   = clock();
    for(int loop=0;loop < threadCount;++loop)
    {   
        void*   result;
        pthread_join(data[loop], &result);
    }
    clock_t                 end   = clock();

   std::cout << threadCount << "\t" << middle - start << "\t" << end - middle << "\t"<< end - start << "\n";

}

我希望Windows在流程创建方面做得更糟 但我希望现代的Unix系统具有相当轻的分叉成本,至少可以与线程相媲美。在较旧的Unix风格的系统上(在fork()之前实现为在写页面上使用副本),它会更糟糕。

无论如何我的时间结果是:

> uname -a
Darwin Alpha.local 10.4.0 Darwin Kernel Version 10.4.0: Fri Apr 23 18:28:53 PDT 2010; root:xnu-1504.7.4~1/RELEASE_I386 i386
> gcc --version | grep GCC
i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5659)
> g++ thread.cpp -o thread -I~/include
> g++ fork.cpp -o fork -I~/include
> foreach a ( 1 2 3 4 5 6 7 8 9 10 12 15 20 30 40 50 60 70 80 90 100 )
foreach? ./thread ${a} >> A
foreach? end
> foreach a ( 1 2 3 4 5 6 7 8 9 10 12 15 20 30 40 50 60 70 80 90 100 )
foreach? ./fork ${a}  >> A
foreach? end
vi A 

Thread:                             Fork:
C   Start   Wait    Total           C   Start   Wait    Total
==============================================================
 1    26     145     171             1   160     37      197
 2    44     198     242             2   290     37      327
 3    62     234     296             3   413     41      454
 4    77     275     352             4   499     59      558
 5    91     107   10808             5   599     57      656
 6    99     332     431             6   665     52      717
 7   130     388     518             7   741     69      810
 8   204     468     672             8   833     56      889
 9   164     469     633             9  1067     76     1143
10   165     450     615            10  1147     64     1211
12   343     585     928            12  1213     71     1284
15   232     647     879            15  1360    203     1563
20   319     921    1240            20  2161     96     2257
30   461    1243    1704            30  3005    129     3134
40   559    1487    2046            40  4466    166     4632
50   686    1912    2598            50  4591    292     4883
60   827    2208    3035            60  5234    317     5551
70   973    2885    3858            70  7003    416     7419
80  3545    2738    6283            80  7735    293     8028
90  1392    3497    4889            90  7869    463     8332
100 3917    4180    8097            100 8974    436     9410

编辑:

做了1000个孩子导致fork版本失败 所以我减少了孩子的数量。但做一次测试似乎也不公平,所以这里有一系列的价值观。

3 个答案:

答案 0 :(得分:10)

嘟m ......我不喜欢你的解决方案有很多原因:

  1. 您没有考虑子进程/线程的执行时间。

  2. 您应该比较cpu-usage而不是经过的时间。这样,您的统计信息将不会依赖于磁盘访问拥塞等。

  3. 让您的孩子进程做点什么。请记住,“modern”fork使用copy-on-write机制来避免在需要之前为子进程分配内存。立即退出太容易了。这样就可以避免fork的所有缺点。

  4. CPU时间不是您需要考虑的唯一成本。内存消耗和IPC的缓慢都是fork解决方案的缺点。

  5. 您可以使用“rusage”代替“clock”来衡量实际资源使用情况。

    P.S。我认为你不能真正衡量编写简单测试程序的进程/线程开销。有太多因素,通常,线程和进程之间的选择是由除了cpu使用之外的其他原因驱动的。

答案 1 :(得分:0)

在Linux下fork是对sys_clone的特殊调用,无论是在库内还是在内核中。克隆有许多开关可以打开和关闭,每个开关都会影响启动的成本。

实际的库函数clone可能比fork更昂贵,因为它做得更多,尽管大多数是在子端(堆栈交换和通过指针调用函数)。

答案 2 :(得分:0)

微基准显示的是线程创建和连接(当我写这个时没有fork结果)需要几十或几百微秒(假设你的系统有CLOCKS_PER_SEC = 1000000,它可能有,因为它是XSI要求)。

既然你说fork()花费了线程成本的3倍,那么我们仍然在谈论最差的十分之一毫秒。如果在应用程序中这是显而易见的,您可以使用进程/线程池,如Apache 1.3。无论如何,我认为启动时间是一个没有实际意义的点。

线程与进程(在Linux和大多数类Unix上)之间的重要区别在于您使用IPC,共享内存(SYSV或mmap样式),管道,套接字(您可以发送)明确选择要共享的进程AF_UNIX套接字上的文件描述符,意味着您可以选择要共享的fd),...在线程上,默认情况下几乎所有内容都是共享的,无论是否需要共享它。事实上,这就是Plan 9有rfork()和Linux有clone()(最近是unshare())的原因,所以你可以选择分享内容。