IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Linux  >

运行时: 管理进程和线程

Linux 和 Windows 上高性能编程技术

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Edward G. Bradford 博士 (egb@us.ibm.com), 高级程序员, IBM 

2002 年 2 月 01 日

Ed 的前一篇专栏文章中,他关注于单一系统中的套接字编程及其性能。在以后的专栏文章中,他将讨论一些他还未完成的话题,但这个月他的主题是 Linux 和 Windows 系统中线程和进程的管理。他将讨论进程和线程之间的区别,演示如何创建和清除它们并编写一个程序,您可以在您的系统上使用该程序来研究线程的管理。请在 论坛上与作者和其他读者分享您对本文的想法。

以前的专栏文章中,我们简略地了解了编程套接字并回顾了完全在单一系统中运行的套接字代码的一些性能评测。这些评测没有反映网络传输速率,但确实显示了套接字可能的最大吞吐量。以后的专栏文章将更详细地讨论它们,并研究实际网络适配器和实际网络上的延迟算法和传输速度。

本月的专栏文章将演示如何创建和清除进程和线程。线程和进程非常相似。实际上,在 Linux 中,几乎无法区别线程和进程。

进程

进程是 Linux 和 Windows 中的可执行程序。按约定,在 Windows 上,文件名中带 .exe 扩展名(后缀)的文件是可执行文件。进程是通过编译源文件并执行来生成的。Windows 和 Linux 上的编译十分相似:


编译一个程序
    WINDOWS:   cl -O2 create-pt2.cpp
    LINUX  :   gcc -O2 create-pt2.cpp -lpthread -o create-pt2

这两种编译的结果都是产生一个可执行文件:在 Windows 上是 create-pt2.exe ,在 Linux 上是 create-pt2 。(不同的环境定义不同的缺省库,因此在编译命令行上使用不同的库规范。)

进程可以继承打开的文件句柄。在 Windows 中,这个句柄值值得怀疑,因为句柄不是小整数。在 Windows 中,没有关于为已打开的文件将句柄初始化为合适值这一过程的记录(我所知道的)。在 Linux 中,将文件描述符设置为值 20,然后使用这个描述符,这相当容易。要这样做只需在正执行的和已执行的程序之间存在一个协议。

一旦创建了进程,Linux 和 Windows 都允许您只要从命令行输入程序名就可启动它:


运行一个程序
    create-pt2

对于 Windows 程序,您不必输入 .exe 后缀。事实上,如果权限允许执行程序并且该程序是合法的 Windows 二进制格式,那么 Windows 就认为任何这样的程序名都是可执行文件。这样,我就可以象 Linux 中那样只需将微软编译器的输出命名为 create-pt2 就可以了。





回页首


创建 Linux 进程

通过使用一个不带任何参数的单一系统调用,Linux 就创建了一个新进程。 fork 系统调用创建父进程的精确副本并向父进程返回子进程的进程标识、对子进程返回零。这里是在 Linux 上创建新进程的代码:


Linux 进程创建
        #include <sys/types.h>
        #include <unistd.h>
        pid_t child_pid;
        child_pid = fork();
        if(child_pid == -1) {
            ERROR;
        }
        else if(child_pid == 0) {
            do_child();
        }
        else {
            do_parent();
        }

Fork 创建新进程,但是如何执行一个新程序呢?这样做的标准方法如下:


在 Linux 上运行新程序
        child_pid = fork();
        if(child_pid == -1) ERROR
        if(child_pid == 0) { // Child executes a new program image
            execl("/bin/view", "view", "/etc/hosts");
            ERROR;
        }
        else {               // Parent waits for child to finish (exit).
            int status;
            while(wait(&status) != child_pid);
        }

创建和执行程序的代码很精练。通过从 main 中返回或者显式地调用 exit 系统调用,程序就退出了。





回页首


创建 Windows 进程

Windows 进程是用 CreateProcess() API 创建的。不象 Linux 的 fork 调用那么简单,在您第一次尝试使用 CreateProcess API 之前,您需要先学习一下。


Windows 上的进程创建 API
BOOL CreateProcess(
    LPCTSTR lpApplicationName,                 // name of executable module
    LPTSTR lpCommandLine,                      // command line string
    LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
    LPSECURITY_ATTRIBUTES lpThreadAttributes,  // SD
    BOOL bInheritHandles,                      // handle inheritance option
    DWORD dwCreationFlags,                     // creation flags
    LPVOID lpEnvironment,                      // new environment block
    LPCTSTR lpCurrentDirectory,                // current directory name
    LPSTARTUPINFO lpStartupInfo,               // startup information
    LPPROCESS_INFORMATION lpProcessInformation // process information
);

为公平起见,应该将 CreateProcess API 与 Linux 上的 execl() 系统调用作比较。尽管 execl() 需要程序名并且在 API 中要将每个参数指定为字符串,但是 CreateProcess() 提供了许多选项。我未研究过所有的可能性,这里我只演示一种可能,并做一些解释。

这个月,我们的 create-pt2.cpp 程序将创建进程或线程。创建进程的代码是:


在 Windows 上创建进程
    extern char *applname;
    extern char *progname;
    HANDLE t1;
    char buf[1024];
    BOOL b;
    #define errno   GetLastError()
    sprintf(buf, "%s-child", applname);
    buf[sizeof(buf)-1] = 0;
    STARTUPINFO         startInfo;
    PROCESS_INFORMATION pidInfo;
    //
    //    child process
    //
    startInfo.cb          = sizeof(STARTUPINFO);
    startInfo.lpReserved  = NULL;
    startInfo.lpTitle     = buf;
    startInfo.lpDesktop   = NULL;
    startInfo.dwX         = 0;
    startInfo.dwY         = 0;
    startInfo.dwXSize     = 0;
    startInfo.dwYSize     = 0;
    startInfo.dwXCountChars    = 0;
    startInfo.dwYCountChars    = 0;
    startInfo.dwFlags     = STARTF_USESTDHANDLES;
    startInfo.wShowWindow = 0; //SW_SHOWDEFAULT;
    startInfo.lpReserved2 = NULL;
    startInfo.cbReserved2 = 0;
    startInfo.hStdInput   = GetStdHandle(STD_INPUT_HANDLE);
    startInfo.hStdOutput  = GetStdHandle(STD_OUTPUT_HANDLE);
    startInfo.hStdError   = GetStdHandle(STD_ERROR_HANDLE);
    b = CreateProcess(
            progname,
            buf,
            NULL,
            NULL,
            TRUE,
            0,//CREATE_NEW_CONSOLE,
            NULL,
            NULL,
            &startInfo,
            &pidInfo);
    if(!b) {
        printf("Creation of child process failed: err=%d\n", errno);
        printf("applname=<%s>, buf=<%s>\n",applname,buf);
        return;
    }
    t1 = pidInfo.hProcess;

正如您看到的,需要的代码量是很大的。要清除进程,其代码类似于 Linux 的 wait() 调用:


在 Windows 中等待进程
    if(WaitForSingleObject(t1,INFINITE) == WAIT_FAILED) {
        printf("WaitForSingleObject FAILED: err=%d\n", GetLastError());
        return;
    }

Windows 程序的退出有几种方法。有两种方法与 Linux 机制很类似。程序可以从 main 上返回,或者调用标准 C 库中的 exit() 例程。第三种方法是调用 ExitProcess() API。没有关于 exitExitProcess 的区别这方面的文档。我们将使用 exit 例程。





回页首


线程

线程是执行上下文。最初每个进程都只有一个执行上下文。这个执行上下文称为线程。如果进程需要另一个执行上下文,那么它只要创建另一个进程。但是,就处理器的周期和内存的使用而言,进程创建的开销是很大的。发明线程是为创建多个执行上下文提供轻量级的机制。Windows 和 Linux 从操作系统上调度线程的目标是提供一个公平的执行环境。

进程和线程之间的最大区别是一个进程的所有线程共享同一个内存空间并共享系统定义的“资源”。这些资源包括打开的文件句柄(文件描述符)、共享内存、进程同步原语和当前目录。因为共享全局内存并且几乎不必分配新内存,所以创建线程比创建进程更简单更快。





回页首


创建 Linux 线程

Linux 支持 POSIX 线程;在帮助页面的第 3 节中有关于 POSIX 线程的文档。可用下面的代码创建 Linux 线程:


在 Linux 上创建线程
        #    define DEC    ( void *(*)(void*) )
        if(pthread_create(&t1, NULL, DEC  threadwork, NULL)) {
            printf("pthread_create A failed: err=%d\n", errno);
            return;
        }

这里, t1 是接收线程标识的参数。第二个参数是支持许多调度选项的线程属性参数。我们将使用缺省设置选项。第三个参数是创建线程时将执行的子例程。第四个参数是向子例程传送的指针。它可以指向为线程或者新建线程要完成操作所需的任何东西而保留的内存。它被正式声明为指向空内存的指针。

通过从子例程返回或者调用 pthread_exit() 例程,Linux 线程就退出了。当父线程调用 pthread_join() 例程时,就回收线程使用的资源。 pthread_join() 在本质上与进程的 wait() 函数很相似。 pthread_join() 不回收线程分配的堆内存。需要由程序或线程来显式地释放全局分配的内存。





回页首


创建 Windows 线程

创建 Windows 线程比创建 Windows 进程要简单得多。线程创建包括的参数和 Linux 使用的相类似,再加上一个安全性描述符和一个初始堆栈大小。


在 Windows 上创建线程
        t1 = CreateThread(NULL,4096,(THRDFN)threadwork,"L",NULL,&threadid);
        if(t1 == NULL) {
            printf("CreateThread FAILED: err=%d\n", errno);
            return;
        }

第一个参数是确定是否能继承线程句柄的安全性描述符。 NULL 意味着不能继承。第二个参数是堆栈的大小。第三和第四个参数是子例程和传入参数。第五个参数是标志参数。如果将标志设置为 CREATE_SUSPENDED ,那么将以挂起状态创建线程。第六个参数是一个指向系统存储结果线程标识位置的指针。

通过从已调用的子例程返回或者调用 ExitThread() API,退出 Windows 线程。父线程通过调用 WaitForSingleObject(threadid) 清除子线程,或者它们可以使用 WaitForMultipleObjects() API 等待多个事件。





回页首


性能结果

为了演示在 Linux 和 Windows 上进程和线程的创建及其相似性,我编写了 create-pt2.cpp 。它使用计时测试的方法,其中输入的是运行测试的秒数。

我在 Red Hat 7.2 (Linux 2.4.2 内核)、Windows 2000 高级服务器和 Windows XP 专业版上运行了该程序。所有这三个操作系统都在含有 320 MB 内存的同一台 Thinkpad 600X 上执行。图 1 和 2 演示了操作系统的性能。


图 1. 线程创建的速度
图 1. 线程创建的速度

图 2. 进程创建的速度
图 2. 进程创建的速度

这些图显示了在创建线程和进程方面,Linux 比 Windows 2000 或 Windows XP 都要快很多。

Windows 2000 和 Windows XP 的结果很不理想,可能有其内在原因,我将搞清这个问题。 create-pt2.cpp 是个非常简单的程序,它只是在给定的时间间隔内尽可能地多次创建并清除线程(或进程)。我运行了如下的测试:


测试脚本
    create-pt 2         # create threads for 2 seconds
    create-pt 4
    create-pt 8
    create-pt 16
    create-pt -p 2      # create processes for 2 seconds
    create-pt -p 4
    create-pt -p 8
    create-pt -p 16

两个 Windows 平台的结果显示:程序运行的时间越长,它们的性能就越糟糕。图 3 演示了这个问题。


图 3. 线程创建速度衰减
图 3. 线程创建速度的衰减

当 Windows 创建一个线程时,它还创建了一个句柄。这个句柄在创建线程的进程上是打开的。在创建进程的例子中,Windows 创建了两个句柄,一个用于进程,另一个用于该进程中正在执行的线程。创建这些句柄是完全没有必要的,因为稍后用 OpenProcess() 或者 OpenThread() API 就可以获得它们。不过,如果不关闭它们,程序将继续获取句柄,同时消耗系统范围的资源。

create-pt2.cpp 的第一个版本没有关闭已创建的线程和进程的句柄。正在衰减的性能清晰地显示了程序的内存泄漏。 通过使用“Task Manager”,并选择“Handles”作为进程选项卡中显示的一栏,我的 create-pt2.exe 程序的句柄数量看上去正在显著增加。句柄泄漏是程序的 Windows 版本的性能随运行时间的延长而降低的原因。

当解决了这个问题后,“Task Manager”向 create-pt2a.cpp 显示:无论运行多长时间,句柄的数量都是固定的。性能结果不再随我运行测试时间的延长而降低。

Windows XP 已改进了线程创建的速度,并超过了 Windows 2000 的速度,但是它还只是 Linux 速度的 60%。同时,除非程序有问题或者评测技术有问题,否则 Windows XP 的进程创建很明显比 Windows 2000 慢,而 Windows 2000 的进程创建比 Linux 慢。





回页首


结束语

我已简要地阐述了进程和线程,并在我编写的程序中演示了它们的编程。在评测了进程和线程创建的性能后,Linux 看起来在这两方面都要比 Windows 2000 或 Windows XP 快。欢迎下载测试脚本 create-pt2a-sh.shcreate-pt2a.cpp 的源代码(请参阅下面的 参考资料)并在您自己的机器上运行它。

如果您知道如何改进代码以使 Windows 进程和线程的创建速度更快,那么我愿听听这样的方法(通过使用 论坛)。



参考资料



关于作者

Ed 管理 IBM 软件组的 Microsoft Premier Support,并为 Linux 和 Windows 软件开发人员撰写每周时事通讯。可以通过 egb@us.ibm.com与 Ed 联系。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款