将 Windows IPC 应用移植到 Linux

随着开发者将原本普遍的 Windows® 应用迁移到 Linux™ 平台,正在进行的向开源迁移的浪潮有可能引发极大的移植问题。这个由三部分构成的系列文章提供一个映射指南,并附有例子,能够简化从 Windows 到 Linux 的转变。第 1 部分介绍了进程和线程。

当前,很多全球商务和服务都正在趋于开源 —— 业界的所有主要参与者都在争取实现此目标。这一趋势催生了一个重要的迁移模式:为不同平台(Windows、OS2、Solaris 等)维持的现有产品将被移植到开放源代码的 Linux 平台。

很多应用程序在设计时并未考虑到需要将它们移植到 Linux。这有可能使移植成为一件痛苦的事情,但并非绝对如此。本系列文章的目的是,帮助您将涉及到 IPC 和线程原语的复杂应用程序从 Windows 迁移到 Linux。我们与您分享迁移这些关键应用程序的经验,包括要求线程同步的多线程应用程序以及要求进程间同步的多进程应用程序。

简言之,可以将此系列文章看作是一个映射文档 —— 它提供了与线程、进程和进程间通信元素(互斥体、信号量等等)相关的各种 Windows 调用到 Linux 调用的映射。我们将那些映射分为三个部分:

  • 第 1 部分涉及的是进程和线程。
  • 第 2 部分处理的是信号量与事件。
  • 第 3 部分涵盖了信号量、关键区域和等待函数。

进程

Windows 中和 Linux 中的基本执行单位是不同的。在 Windows 中,线程是基本执行单位,进程是一个容纳线程的容器。

在 Linux 中,基本执行单位是进程。Windows API 所提供的功能可以直接映射到 Linux 系统调用:

表 1. 进程映射

Windows Linux 类别
CreateProcess()
CreateProcessAsUser()
fork()
setuid()
exec()
可映射
TerminateProcess() kill() 可映射
SetThreadpriority()
GetThreadPriority()
Setpriority()
getPriority()
可映射
GetCurrentProcessID() getpid() 可映射
Exitprocess() exit() 可映射
Waitforsingleobject()
Waitformultipleobject()
GetExitCodeProcess()
waitpid()
Using Sys V semaphores, Waitforsingleobject/multipleobject
不能实现
与上下文相关
GetEnvironmentVariable
SetEnvironmentVariable
getenv()
setenv()
可映射

“类别”一列(解释了本文中所使用的分类结构)表明了 Windows 结构是否 可映射 或者 与上下文相关

  • 如果可映射,则 Windows 结构可以映射到特定的 Linux 结构(需要仔细检查类型、参数、返回代码等)。Windows 和 Linux 结构都提供了类似的功能。
  • 如果是与上下文相关,则 Linux 中可能有(也可能没有)相应于给定的 Windows 结构的结构,或者 Linux 可能有不只一个提供类似功能的结构。无论是哪种情况,都要根据应用程序上下文才能确定要使用哪个特定的 Linux 结构。

创建进程

在 Windows 中,您可以使用 CreateProcess() 来创建一个新的进程。 CreateProcess() 函数创建一个新的进程及其主线程,如下:

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
)

bInheritHandles 确定了子进程是否要继承父进程的句柄。lpApplicationNamelpCommandLine 给出了将要被启动的进程的名称与路径。lpEnvironment 定义了进程可使用的环境变量。

在 Linux 中,exec* 家族函数使用一个新的进程映像取代当前进程映像(如下所示):

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int  execle(const  char  *path,  const  char  *arg  , ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

exec* 的这些版本只是内核函数 execve()int execve(const char *filename, char *const argv [], char *const envp[]))的各种调用接口。在这里,argv 是包含有参数 list 的指针,envp 是包含有环境变量列表(主要是 key=value 对)的指针。

它必须与 fork() 命令一起使用,所以父进程和子进程都在运行: pid_t fork(void)fork() 会创建一个子进程,与父进程相比只是 PID 和 PPID 不同;实际上,资源利用设为 0。

默认情况下,exec() 继承父进程的组和用户 ID,这就使得它会依赖于父进程。可以使用以下方法来改变:

  • 设置指定程序文件的 set-uidset-gid
  • 使用 setpgid()setuid() 系统调用

CreateProcessAsUser() 函数与 CreateProcess() 类似,只是新进程是在用户通过 hToken 参数描述的安全上下文中运行。在 Linux 中,没有与此函数惟一对应的函数,但是可以使用下面的逻辑来实现对它的复制:

  • 使用 fork() 创建一个具有新的 PID 的子进程
  • 使用 setuid() 切换到那个新的 PID
  • 使用 exec() 将现有进程改变为将要执行的进程

终止进程

在 Windows 中,您可以使用 TerminateProcess() 强制终止一个运行中的进程。

BOOL TerminateProcess(
  HANDLE hProcess, // handle to the process
  UINT uExitCode   // exit code for the process
);

这个函数终止运行中的进程及其相关线程。只是在非常极端的场合才会使用这个函数。

在 Linux 中,您可以使用 kill() 来强行杀死一个进程: int kill(pid_t pid, int sig)。这个系统调用会终止 id 为 PID 的进程。您也可以使用它向任何组或者进程发出信号。

使用等待函数

在子进程依赖于父进程的情况下,您可以在父进程中使用等待函数来等待子进程的终止。在 Windows 中,您可以使用 WaitForSingleObject() 函数调用来实现此功能。

您可以使用 WaitForMultipleObject() 函数来等待多个对象。

DWORD WaitForMultipleObjects(
  DWORD nCount,             // number of handles in array
  CONST HANDLE *lpHandles,  // object-handle array
  BOOL bWaitAll,            // wait option
  DWORD dwMilliseconds      // time-out interval
);

您可以向对象句柄数组(object-handle array)中填充很多需要等待的对象。根据 bWaitALL 选项,您既可以等待所有对象被信号通知,也可以等待其中任意一个被信号通知。

在这两个函数中,如果您想等待有限的一段时间,则可以在第二个参数中指定时间间隔。如果您想无限制等待,那么使用 INFINITE 作为 dwMilliseconds 的值。将 dwMilliseconds 设置为 0 则只是检测对象的状态并返回。

在 Linux 中,如果您希望无限期等待进程被杀死,则可以使用 waitpid()。在 Linux 中,使用 waitpid() 调用无法等待限定的时间。

在这段代码中:pid_t waitpid(pid_t pid, int *status, int options)waitpid() 会无限期等待子进程的终止。在 Windows 和 Linux 中,等待函数会挂起当前进程的执行,直到它完成等待,不过,在 Windows 中可以选择在指定的时间后退出。使用 System V 信号量,您可以实现类似于 WaitForSingleObject()WaitForMultipleObject() 的限时等待或者 NO WAIT 功能,在本系列的第 2 部分中将讨论此内容。本系列的第 3 部分将深入讨论等待函数。

退出进程

退出进程指的是优雅(graceful)地退出进程,并完成适当的清除工作。在 Windows 中,您可以使用 ExitProcess() 来执行此操作。

VOID ExitProcess(
  UINT uExitCode   // exit code for all threads
);

ExitProcess() 是在进程结束处执行的方法。这个函数能够干净地停止进程。包括调用所有链接到的动态链接库(DLL)的入口点函数,给出一个值,指出这个进程正在解除那个 DLL 的链接。

Linux 中与 ExitProcess() 相对应的是 exit()void exit(int status);

exit() 函数会令程序正常终止,并将 &0377 状态值返回给父进程。 C 语言标准规定了两个定义(EXIT_SUCCESSEXIT_FAILURE),可以被传递到状态参数,以说明终止成功或者不成功。

环境变量

每个进程都拥有关联到它的一组环境,其中主要是 name=value 对,指明进程可以访问的各种环境变量。尽管我们可以在创建进程时指定环境,不过也有特定函数可以在进程创建后设置和获得环境变量。

在 Windows 中,您可以使用 GetEnvironmentVariable()SetEnvironmentVariable() 来获得和设置环境变量。

DWORD GetEnvironmentVariable(
  LPCTSTR lpName,  // environment variable name
  LPTSTR lpBuffer, // buffer for variable value
  DWORD nSize      // size of buffer
);

如果成功,则此函数返回值缓存的大小,如果指定的名称并不是一个合法的环境变量名,则返回 0。 SetEnvironmentVariable() 函数为当前进程设置指定的环境变量的内容。

BOOL SetEnvironmentVariable(
  LPCTSTR lpName,  // environment variable name
  LPCTSTR lpValue  // new value for variable
);

如果函数成功,则返回值非零。如果函数失败,则返回值为零。

在 Linux 中,getenv()setenv() 系统调用提供了相应的功能。

char *getenv(const char *name);
int setenv(const char *name, const char *value, int overwrite);

getenv() 函数会在环境列表中搜索与名称字符串相匹配的字符串。这个函数会返回一个指向环境中的值的指针,或者如果不匹配则返回 NULL。setenv() 函数将变量名和值添加到环境中,如果那个名称并不存在。如果环境中已经存在那个名称,而且如果 overwrite 非零,则它的值会被修改为 value。如果 overwrite 为零,则 name 的值不会被改变。如果成功,则 setenv() 会返回零,如果环境中空间不足,则返回 -1。

例子

下面的例子解释了我们在本节中讨论的内容。


清单 1. Windows 进程代码

				
//Sample Application that explain process concepts
//Parameters Declaration/Definition
int TimetoWait;
STARTUPINFO si;
PROCESS_INFORMATION pi;
LPTSTR lpszCurrValue,LPTSTR lpszVariable;
TCHAR tchBuf[BUFSIZE];
BOOL fSuccess;
if(argc > 2)
{
    printf("InvalidArgument");
    ExitProcess(1); //Failure
}
//Get and display an  environment variable PATH
lpszCurrValue = ((GetEnvironmentVariable("PATH",tchBuf, BUFSIZE) > 0) ? tchBuf : NULL);
lpszVariable = lpszCurrValue;
//Display the environment variable
while (*lpszVariable)
    putchar(*lpszVariable++);
putchar('/n');
//Initialise si and pi
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
//Create a childProcess
if( !CreateProcess( NULL,             // No module name (use command line).
                    "SomeProcess",    // Command line.
                    NULL,             // Process handle not inheritable.
                    NULL,             // Thread handle not inheritable.
                    FALSE,            // Set handle inheritance to FALSE.
                    0,                // No creation flags.
                    NULL,             // Use parent's environment block.
                    NULL,             // Use parent's starting directory.
                    &si,              // Pointer to STARTUPINFO structure.
                    &pi )             // Pointer to PROCESS_INFORMATION structure.
                        )
{
    printf( "CreateProcess failed." );
}
// Wait until child process exits.
if(argc == 2)
{
    TIMEOUT = atoi(argv[1]);
    ret = WaitForSingleObject( pi.hProcess, TIMEOUT );
    if(ret == WAIT_TIMEOUT)
    {
        TerminateProcess(pi.hProcess);
    }
}
else
{
    WaitForSingleObject( pi.hProcess, INFINITE );
    ...
}
    ExitProcess(0); //Success


清单 2. 相应的 Linux 进程代码

				
#include <stdlib.h>
int main(int argc,char *argv[])
{
    //Parameters Declaration/Definition
    char PathName[255];
    char *Argptr[20];
    int rc;
    char *EnvValue,*lpszVariable;
    if(argc > 1)
    {
        printf(" Wrong parameters !!");
        exit(EXIT_FAILURE);
    }
    //Get and display an  environment variable PATH
    EnvValue = getenv("PATH");
    if(EnvValue == NULL)
    {
        printf("Invalid environment variable passed as param !!");
    }else
    {
        lpszVariable = EnvValue;
        while (*lpszVariable)
            putchar(*lpszVariable++);
        putchar('/n');
    }
    rc = fork(); //variable rc's value on success would be process ID in the parent
                 //process, and 0 in the child's thread of execution.
    switch(rc)
    {
        case -1:
            printf("Fork() function failed !!");
            ret = -1;
            break;
        case 0:
            printf("Child process...");
            setpgid(0,0);  //Change the parent grp ID to 0
        ret = execv(PathName,Argptr); // there are other flavours of exec available,
                                      // u can use any of them based on the arguments.
        if(ret == -1)
        {
            kill(getpid(),0);
        }
        break;
         default:
             // infinitely waits for child process to die
             Waitpid(rc,&status,WNOHANG);
             //Note RC will have PID returned since this is parent process.
             break;
    }
    exit(EXIT_SUCCESS);
}





回页首


线程

在 Windows 中,线程是基本的执行单位。在进程的上下文中会有一个或多个线程在运行。调度代码在内核中实现。没有单独的“调度器(scheduler)”模块或例程。

Linux 内核使用的是进程模型,而不是线程模型。Linux 内核提供了一个轻量级进程框架来创建线程;实际的线程在用户空间中实现。在 Linux 中有多种可用的线程库(LinuxThreads、NGPT、NPTL 等等)。本文中的资料基于 LinuxThreads 库,不过这里的资料也适用于 Red Hat 的 Native POSIX Threading Library(NPTL)。

本节描述 Windows 和 Linux 中的线程。内容涵盖了创建线程、设置其属性以及修改其优先级。

表 2. 线程映射

Windows Linux 类别
CreateThread pthread_create
pthread_attr_init
pthread_attr_setstacksize
pthread_attr_destroy
可映射
ThreadExit pthread_exit 可映射
WaitForSingleObject pthread_join
pthread_attr_setdetachstate
pthread_detach
可映射
SetPriorityClass
SetThreadPriority
setpriority
sched_setscheduler
sched_setparam

pthread_setschedparam
pthread_setschedpolicy
pthread_attr_setschedparam
pthread_attr_setschedpolicy
与上下文相关

创建线程

在 Windows 中,您可以使用 CreateThread() 来创建线程,创建的线程在调用进程的虚拟地址空间中运行。

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes,     // SD
  SIZE_T dwStackSize,                           // initial stack size
  LPTHREAD_START_ROUTINE lpStartAddress,        // thread function
  LPVOID lpParameter,                           // thread argument
  DWORD dwCreationFlags,                        // creation option
  LPDWORD lpThreadId                            // thread identifier
);

lpThreadAttributes 是指向线程属性的指针,决定了线程句柄是否能由子进程继承。

Linux 使用 pthread 库调用 pthread_create() 来派生线程:

int pthread_create (pthread_t *thread_id, pthread_attr_t *threadAttr,
                    void * (*start_address)(void *), void * arg);

注意:在 Windows 中,受可用虚拟内存的限制,一个进程可以创建的线程数目是有限的。默认情况下,每个线程有一兆栈空间。因此,您最多可以创建 2,028 个线程。如果您减小默认栈大小,那么可以创建更多线程。在 Linux 中,使用 ULIMIT -a(limits for all users)可以获得每个用户可以创建的线程的最大数目,可以使用 ULIMIT -u 来修改它,不过只有在登录时才可以这样做。 /usr/Include/limit.h 和 ulimit.h 下的头文件定义了这些内容。您可以修改它们并重新编译内核,以使其永久生效。对于 POSIX 线程限制而言,local_lim.h 中定义的 THREAD_THREADS_MAX 宏定义了数目的上限。

指定线程函数

CreateThread() 中的 lpStartAddress 参数是刚创建的线程要执行的函数的地址。

pthread_create() 库调用的 start_address 参数是刚创建的线程要执行的函数的地址。

传递给线程函数的参数

在 Windows 中,系统调用 CreateThread() 的参数 lpParameter 指定了要传递给刚创建的线程的参数。它指明了将要传递给新线程的数据条目的地址。

在 Linux 中,库调用 pthread_create() 的参数 arg 指定了将要传递给新线程的参数。

设置栈大小

在 Windows 中,CreateThread() 的参数 dwStackSize 是将要分配给新线程的以字节为单位的栈大小。栈大小应该是 4 KB 的非零整数倍,最小为 8 KB。

在 Linux 中,栈大小在线程属性对象中设置;也就是说,将类型为 pthread_attr_t 的参数 threadAttr 传递给库调用 pthread_create()。在设置任何属性之前,需要通过调用 pthread_attr_init() 来初始化这个对象。使用调用 pthread_attr_destroy() 来销毁属性对象:

int pthread_attr_init(pthread_attr_t *threadAttr);
int pthread_attr_destroy(pthread_attr_t *threadAttr);

注意,所有 pthread_attr_setxxxx 调用都有与 pthread_xxxx 调用(如果有)类似的功能,只是您只能在线程创建之前使用 pthread_attr_xxxx,来更新将要作为参数传递给 pthread_create 的属性对象。同时,您在创建线程之后的任意时候都可以使用 pthread_xxxx

使用调用 pthread_attr_setstacksize() 来设置栈大小: int pthread_attr_setstacksize(pthread_attr_t *threadAttr, int stack_size);

退出线程

在 Windows 中,系统调用 ExitThread() 会终止线程。 dwExitCode 是线程的返回值,另一个线程通过调用 GetExitCodeThread() 就可以得到它。

VOID ExitThread(
  DWORD dwExitCode   // exit code for this thread
);

Linux 中与此相对应的是库调用 pthread_exit()retval 是线程的返回值,可以在另一个线程中通过调用 pthread_join() 来获得它: int pthread_exit(void* retval);

线程状态

在 Windows 中,没有保持关于线程终止的显式线程状态。不过,WaitForSingleObject() 让线程能够显式地等待进程中某个指定的或者非指定的线程终止。

在 Linux 中,默认以可连接(joinable)的状态创建线程。在可连接状态中,另一个线程可以同步这个线程的终止,使用函数 pthread_join() 来重新获得其终止代码。可连接的线程只有在被连接后才释放线程资源。

Windows 使用 WaitForSingleObject() 来等待某个线程终止:

DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD dwMilliseconds
);

其中:

  • hHandle 是指向线程句柄的指针。
  • dwMilliseconds 是以毫秒为单位的超时值。如果这个值被设置为 INFINITE,则它会无限期地阻塞进行调用的线程/进程。

Linux 使用 pthread_join() 来完成同样的功能: int pthread_join(pthread_t *thread, void **thread_return);

在分离的状态中,线程终止后线程资源会立即被释放。通过对线程属性对象调用 pthread_attr_setdetachstate() 可以设置分离状态: int pthread_attr_setdetachstate (pthread_attr_t *attr, int detachstate);。以可连接状态创建的线程,稍后可以被转为分离状态,方法是使用 pthread_detach() 调用:int pthread_detach (pthread_t id);

改变优先级

在 Windows 中,线程的优先级由其进程的优先级等级以及进程优先级等级中的线程优先级层次决定。在 Linux 中,线程本身就是一个执行单位,有其自己的优先级。它与其进程的优先级没有依赖关系。

在 Windows 中,您可以使用 SetPriorityClass() 来设置特定进程的优先级等级:

BOOL SetPriorityClass(
  HANDLE hProcess,         // handle to the process
  DWORD dwPriorityClass    // Priority class
);

dwPriorityClass 是进程的优先级等级,它可以设置为下列值中的任意一个:

  • IDLE_PRIORITY_CLASS
  • BELOW_NORMAL_PRIORITY_CLASS
  • NORMAL_PRIORITY_CLASS
  • ABOVE_NORMAL_PRIORITY_CLASS
  • HIGH_PRIORITY_CLASS
  • REALTIME_PRIORITY_CLASS

一旦设置了进程的优先级等级,就可以使用 SetThreadPriority() 在进程的优先级等级内部设置线程的优先级层次:

BOOL SetThreadPriority(
  HANDLE hThread,
  int nPriority
);

nPriority 是线程的优先级值,它被设置为下列之一;

  • THREAD_PRIORITY_ABOVE_NORMAL 将优先级设置为比优先级等级高 1 级。
  • THREAD_PRIORITY_BELOW_NORMAL 将优先级设置为比优先级等级低 1 级。
  • THREAD_PRIORITY_HIGHEST 将优先级设置为比优先级等级高 2 级。
  • THREAD_PRIORITY_IDLEIDLE_PRIORITY_CLASSBELOW_NORMAL_PRIORITY_CLASSNORMAL_PRIORITY_CLASSABOVE_NORMAL_PRIORITY_CLASSHIGH_PRIORITY_CLASS 进程将基优先级设置 1,为 REALTIME_PRIORITY_CLASS 进程将基优先级设置为 16。
  • THREAD_PRIORITY_LOWEST 将优先级设置为比优先级等级低 2 级。
  • THREAD_PRIORITY_NORMAL 为优先级等级设置为普通优先级。
  • THREAD_PRIORITY_TIME_CRITICALIDLE_PRIORITY_CLASSBELOW_NORMAL_PRIORITY_CLASSNORMAL_PRIORITY_CLASSABOVE_NORMAL_PRIORITY_CLASSHIGH_PRIORITY_CLASS 进程将基优先级设置 15,为 REALTIME_PRIORITY_CLASS 进程将基优先级设置为 31。





回页首


进程和线程的例子

为了结束这一期文章,让我们来看下面类型的进程和线程的一些例子:

  • 普通的或者常规的进程和线程
  • 对时间要求严格的(time-critical)或者实时的进程和线程

普通的或常规的进程/线程

使用 Linux 系统调用 setpriority() 来设置或者修改普通进程和线程的优先级层次。参数的范围是 PRIO_PROCESS。将 id 设置为 0 来修改当前进程(或线程)的优先级。此外,delta 是优先级的值 —— 这一次是从 -20 到 20。另外,要注意在 Linux 中较低的 delta 值代表较高的优先级。所以,使用 +20 设置 IDLETIME 优先级,使用 0 设置 REGULAR 优先级。

在 Windows 中,常规线程的优先级的范围是从 1(较低的优先级)到 15(较高的优先级)。不过,在 Linux 中,普通非实时进程的优先级范围是从 -20(较高的)到 +20(较低的)。在使用之前必须对此进行映射: int setpriority(int scope, int id, int delta);

对时间要求严格的和实时的进程和线程

您可以使用 Linux 系统调用 sched_setscheduler() 来修改正在运行的进程的调度优先级: int sched_setscheduler(pit_t pid, int policy, const struct sched_param *param);

参数 policy 是调度策略。policy 的可能的值是 SCHED_OTHER (常规的非实时调度)、SCHED_RR(实时 round-robin 策略)和 SCHED_FIFO(实时 FIFO 策略)。

在此,param 是指向描述调度优先级结构体的指针。它的范围是 1 到 99,只用于实时策略。对于其他的(普通的非实时进程),它为零。

在 Linux 中,作为一个大家所熟知的调度策略,也可以通过使用系统调用 sched_setparam 来仅修改进程优先级: int sched_setparam(pit_t pid, const struct sched_param *param);

LinuxThreads 库调用 pthread_setschedparamsched_setscheduler 的线程版本,用于动态修改运行着的线程的调度优先级和策略: int pthread_setschedparam(pthread_t target_thread, int policy, const struct sched_param *param);

参数 target_thread 告知线程要修改谁的优先级;param 指定了优先级。

LinuxThreads 库会调用 pthread_attr_setschedpolicy,并且您可以在线程被创建之前使用 pthread_attr_setschedparam 来设置线程属性对象的调度策略和优先级层次:

int pthread_attr_setschedpolicy(pthread attr_t *threadAttr, int policy);
int pthread_attr_setschedparam(pthread attr_t *threadAttr, const struct sched_param *param);

在 Windows 中,实时线程的优先级范围是从 16(较低的优先级)到 31(较高的优先级)。在 Linux 中,实时线程的优先级范围是从 99(较高的)到 1(较低的优先级)。在使用前必须对此进行映射。

例子

下面的清单阐述了本节中的概念。


清单 3. Windows 线程示例

				
				Main Thread
enum stackSize = 120 * 1024 ;
// create a thread normal and real time thread
DWORD  normalTId, realTID;
HANDLE normalTHandle, realTHandle;
normalTHandle = CreateThread(
                NULL,            // default security attributes
                stackSize,       // 120K
                NormalThread,    // thread function
                NULL,            // argument to thread function
                0,               // use default creation flags
                &normalTId);     // returns the thread identifier
// Set the priority class as "High priority"
SetPriorityClass(pHandle, HIGH_PRIORITY_CLASS);
normalTHandle = CreateThread(
                 NULL,            // default security attributes
                 stackSize,       // 120K
                 NormalThread,    // thread function
                 NULL,            // argument to thread function
                 0,               // use default creation flags
                &normalTId);     // returns the thread identifier
CloseHandle(threadHandle);
...
...
// Thread function
DWORD WINAPI NormalThread ( LPVOID lpParam )
{
    HANDLE tHandle,pHandle;
    pHandle = GetCurrentProcess();
    tHandle = GetCurrentThread();
    // Set the priority class as "High priority"
    SetPriorityClass(pHandle, HIGH_PRIORITY_CLASS);
    // increase the priority by 2 points above the priority class
    SetThreadPriority(tHandle,THREAD_PRIORITY_HIGHEST);
    // perform job at high priority
    ...
    ...
    ...
    // Reset the priority class as "Normal"
   SetPriorityClass(pHandle, NORMAL_PRIORITY_CLASS);
    // set the priority back to normal
    SetThreadPriority(tHandle,THREAD_PRIORITY_NORMAL);
    // Exit thread
    ExitThread(0);
}
// Thread function
DWORD WINAPI RealTimeThread ( LPVOID lpParam )
{
    HANDLE tHandle, pHandle ;
    pHandle = GetCurrentProcess();
    tHandle = GetCurrentThread  ();
    // Set the priority class as "Real time"
   SetPriorityClass(pHandle, REALTIME_PRIORITY_CLASS);
    // increase the priority by 2 points above the priority class
    SetThreadPriority(tHandle,THREAD_PRIORITY_HIGHEST);
    // do time critical work
    ...
    ...
    ...
    // Reset the priority class as "Normal"
   SetPriorityClass(pHandle, NORMAL_PRIORITY_CLASS);
    // Reset the priority back to normal
    SetThreadPriority(tHandle,THREAD_PRIORITY_NORMAL);
    ExitThread(0);
}


清单 4. Linux 相应的线程代码

				
static void * RegularThread (void *);
static void * CriticalThread (void *);
// Main Thread
       pthread_t thread1, thread2;  // thread identifiers
       pthread_attr_t threadAttr;
       struct sched_param param;  // scheduling priority
       // initialize the thread attribute
       pthread_attr_init(&threadAttr);
       // Set the stack size of the thread
       pthread_attr_setstacksize(&threadAttr, 120*1024);
       // Set thread to detached state. No need for pthread_join
       pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED);
       // Create the threads
       pthread_create(&thread1, &threadAttr, RegularThread, NULL);
       pthread_create(&thread2, &threadAttr, CriticalThread,NULL);
       // Destroy the thread attributes
       pthread_attr_destroy(&threadAttr);
       ...
       ...
  // Regular non-realtime Thread function
  static void * RegularThread (void *d)   {
       int priority = -18;
       // Increase the priority
       setpriority(PRIO_PROCESS, 0, priority);
       // perform high priority job
       ...
       ...
       // set the priority back to normal
       setpriority(PRIO_PROCESS, 0, 0);
       pthread_exit(NULL);
   }
   // Time Critical Realtime Thread function
   static void * CriticalThread (void *d) {
       // Increase the priority
        struct sched_param param;  // scheduling priority
        int policy = SCHED_RR;     // scheduling policy
        // Get the current thread id
        pthread_t thread_id = pthread_self();
        // To set the scheduling priority of the thread
        param.sched_priority = 90;
        pthread_setschedparam(thread_id, policy, &param);
        // Perform time critical task
        ...
        ...
        // set the priority back to normal
        param.sched_priority = 0;
        policy = 0;             // for normal threads
        pthread_setschedparam(thread_id, policy, &param);
        ....
        ....
        pthread_exit(NULL);
   }

同步

在 Windows 上,同步是使用等待函数中的同步对象来实现的。同步对象可以有两种状态:有信号(signaled)状态和无信号(non-signaled)状态。当在一个等待函数中使用同步对象时,等待函数就会阻塞调用线程,直到同步对象的状态被设置为有信号为止。

下面是在 Windows 上可以使用的一些同步对象:

  • 事件(Event)
  • 信号量(Semaphore)
  • 互斥(Mutexe)
  • 临界区(Critical section)

在 Linux 中,可以使用不同的同步原语。Windows 与 Linux 的不同之处在于每个原语都有自己的等待函数(所谓等待函数就是用来修改同步原语状态的函数);在 Windows 中,有一些通用的等待函数来实现相同的目的。以下是 Linux 上可以使用的一些同步原语:

  • 信号量(Semaphore)
  • 条件变量(Conditional variable)
  • 互斥(Mutexe)

通过使用上面列出的这些原语,各种库都可以用于 Linux 之上,以提供同步机制。

表 1. 同步映射

Windows Linux —— 线程 Linux —— 进程
互斥 互斥 - pthread 库 System V 信号量
临界区 互斥 - pthread 库 不适用,因为临界区只用于同一进程的不同线程之间
信号量 具有互斥的条件变量 - pthreads
POSIX 信号量
System V 信号量
事件 具有互斥的条件变量 - pthreads System V 信号量





回页首


信号量

Windows 信号量是一些计数器变量,允许有限个线程/进程访问共享资源。Linux POSIX 信号量也是一些计数器变量,可以用来在 Linux 上实现 Windows 上的信号量功能。

在对进程进行映射时,我们需要考虑以下问题:

  • 信号量的类型: Windows 提供了有名(named)信号量和无名(unnamed)信号量。有名信号量可以在进程之间进行同步。在 Linux 上,在相同进程的不同线程之间,则只使用 POSIX 信号量。在进程之间,可以使用 System V 信号量。
  • 等待函数中的超时: 当在一个等待函数中使用时,可以为 Windows 信号量对象指定超时值。在 Linux 中,并没有提供这种功能,只能通过应用程序逻辑处理超时的问题。

表 2. 信号量映射

Windows Linux 线程 Linux 进程 类别
CreateSemaphore sem_init semget
semctl
与上下文相关
OpenSemaphore 不适用 semget 与上下文相关
WaitForSingleObject sem_wait
sem_trywait
semop 与上下文相关
ReleaseSemaphore sem_post semop 与上下文相关
CloseHandle sem_destroy semctl 与上下文相关

创建信号量

在 Windows 中,可以使用 CreateSemaphore() 创建或打开一个有名或无名的信号量。

HANDLE CreateSemaphore(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
  LONG lInitialCount,
  LONG lMaximumCount,
  LPCTSTR lpName
);

在这段代码中:

  • lpSemaphoreAttributes 是一个指向安全性属性的指针。如果这个指针为空,那么这个信号量就不能被继承。
  • lInitialCount 是该信号量的初始值。
  • lMaximumCount 是该信号量的最大值,该值必须大于 0。
  • lpName 是信号量的名称。如果该值为 NULL,那么这个信号量就只能在相同进程的不同线程之间共享。否则,就可以在不同的进程之间进行共享。

这个函数创建信号量,并返回这个信号量的句柄。它还将初始值设置为调用中指定的值。这样就可以允许有限个线程来访问某个共享资源。

在 Linux 中,可以使用 sem_init() 来创建一个无名的 POSIX 信号量,这个调用可以在相同进程的线程之间使用。它还会对信号量计数器进行初始化:int sem_init(sem_t *sem, int pshared, unsigned int value)。在这段代码中:

  • value(信号量计数器)是这个信号量的初始值。
  • pshared 可以忽略,因为在目前的实现中,POSIX 信号量还不能在进程之间进行共享。

这里要注意的是,最大值基于 demaphore.h 头文件中定义的 SEM_VALUE_MAX。

在 Linux 中,semget() 用于创建 System V 信号量,它可以在不同集成的线程之间使用。可以用它来实现与 Windows 中有名信号量相同的功能。这个函数返回一个信号量集标识符,它与一个参数的键值关联在一起。当创建一个新信号量集时,对于与 semid_ds 数据结构关联在一起的信号量,semget() 要负责将它们进行初始化,方法如下:

  • sem_perm.cuidsem_perm.uid 被设置为调用进程的有效用户 ID。
  • sem_perm.cgidsem_perm.gid 被设置为调用进程的有效组 ID。
  • sem_perm.mode 的低 9 位被设置为 semflg 的低 9 位。
  • sem_nsems 被设置为 nsems 的值。
  • sem_otime 被设置为 0。
  • sem_ctime 被设置为当前时间。

用来创建 System V 信号量使用的代码是:int semget(key_t key, int nsems, int semflg)。下面是对这段代码的一些解释:

  • key 是一个惟一的标识符,不同的进程使用它来标识这个信号量集。我们可以使用 ftok() 生成一个惟一的键值。IPC_PRIVATE 是一个特殊的 key_t 值;当使用 IPC_PRIVATE 作为 key 时,这个系统调用就会只使用 semflg 的低 9 位,但却忽略其他内容,从而新创建一个信号量集(在成功时)。
  • nsems 是这个信号量集中信号量的数量。
  • semflg 是这个新信号量集的权限。要新创建一个信号量集,您可以将使用 IPC_CREAT 来设置位操作或访问权限。如果具有该 key 值的信号量集已经存在,那么 IPC_CREAT/IPC_EXCL 标记就会失败。

注意,在 System V 信号量中,key 被用来惟一标识信号量;在 Windows 中,信号量是使用一个名称来标识的。

为了对信号量集数据结构进行初始化,可以使用 IPC_SET 命令来调用 semctl() 系统调用。将 arg.buf 所指向的 semid_ds 数据结构的某些成员的值写入信号量集数据结构中,同时更新这个结构的 sem_ctime member 的值。用户提供的这个 arg.buf 所指向的 semid_ds 结构如下所示:

  • sem_perm.uid
  • sem_perm.gid
  • sem_perm.mode (只有最低 9 位有效)

调用进程的有效用户 ID 应该是超级用户,或者至少应该与这个信号量集的创建者或所有者匹配: int semctl(int semid, int semnum, int cmd = IPC_SET, ...)。在这段代码中:

  • semid 是信号量集的标识符。
  • semnum 是信号量子集偏移量(从 0 到 nsems -1,其中 n 是这个信号量集中子集的个数)。这个命令会被忽略。
  • cmd 是命令;它使用 IPC_SET 来设置信号量的值。
  • args 是这个信号量集数据结构中要通过 IPC_SET 来更新的值(在这个例子中会有解释)。

最大计数器的值是根据在头文件中定义的 SEMVMX 来决定的。

打开信号量

在 Windows 中,我们使用 OpenSemaphore() 来打开某个指定信号量。只有在两个进程之间共享信号量时,才需要使用信号量。在成功打开信号量之后,这个函数就会返回这个信号量的句柄,这样就可以在后续的调用中使用它了。

HANDLE OpenSemaphore(
  DWORD dwDesiredAccess,
  BOOL bInheritHandle,
  LPCTSTR lpName
)

在这段代码中:

  • dwDesiredAccess 是针对该信号量对象所请求的访问权。
  • bInheritHandle 是用来控制这个信号量句柄是否可继承的标记。如果该值为 TRUE,那么这个句柄可以被继承。
  • lpName 是这个信号量的名称。

在 Linux 中,可以调用相同的 semget() 来打开某个信号量,不过此时 semflg 的值为 0:int semget(key,nsems,0)。在这段代码中:

  • key 应该指向想要打开的信号量集的 key 值。
  • 为了打开一个已经存在的信号量,可以将 nsems 和标记设置为 0。semflg 值是在返回信号量集标识符之前对访问权限进行验证时设置的。

获取信号量

在 Windows 中,等待函数提供了获取同步对象的机制。可以使用的等待函数有多种类型;在这一节中,我们只考虑 WaitForSingleObject()(其他类型将会分别进行讨论)。这个函数使用一个信号量对象的句柄作为参数,并会一直等待下去,直到其状态变为有信号状态或超时为止。

DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );

在这段代码中:

  • hHandle 是指向互斥句柄的指针。
  • dwMilliseconds 是超时时间,以毫秒为单位。如果该值是 INFINITE,那么它阻塞调用线程/进程的时间就是不确定的。

在 Linux 中,sem_wait() 用来获取对信号量的访问。这个函数会挂起调用线程,直到这个信号量有一个非空计数为止。然后,它可以原子地减少这个信号量计数器的值:int sem_wait(sem_t * sem)

在 POSIX 信号量中并没有超时操作。这可以通过在一个循环中执行一个非阻塞的 sem_trywait() 实现,该函数会对超时值进行计算:int sem_trywait(sem_t * sem)

在使用 System V 信号量时,如果通过使用 IPC_SET 命令的 semctl() 调用设置初始的值,那么必须要使用 semop() 来获取信号量。semop() 执行操作集中指定的操作,并阻塞调用线程/进程,直到信号量值为 0 或更大为止:int semop(int semid, struct sembuf *sops, unsigned nsops)

函数 semop() 原子地执行在 sops 中所包含的操作 —— 也就是说,只有在这些操作可以同时成功执行时,这些操作才会被同时执行。sops 所指向的数组中的每个 nsops 元素都使用 struct sembuf 指定了一个要对信号量执行的操作,这个结构包括以下成员:

  • unsigned short sem_num; (信号量个数)
  • short sem_op; (信号量操作)
  • short sem_flg; (操作标记)

要获取信号量,可以通过将 sem_op 设置为 -1 来调用 semop();在使用完信号量之后,可以通过将 sem_op 设置为 1 来调用 semop() 释放信号量。通过将 sem_op 设置为 -1 来调用 semop(),信号量计数器将会减小 1,如果该值小于 0(信号量的值是不能小于 0 的),那么这个信号量就不能再减小,而是会让调用线程/进程阻塞,直到其状态变为有信号状态为止。

sem_flg 中可以识别的标记是 IPC_NOWAITSEM_UNDO。如果某一个操作被设置了 SEM_UNDO 标记,那么在进程结束时,该操作将被取消。如果 sem_op 被设置为 0,那么 semop() 就会等待 semval 变成 0。这是一个“等待为 0” 的操作,可以用它来获取信号量。

记住,超时操作在 System V 信号量中并不适用。这可以在一个循环中使用非阻塞的 semop()(通过将 sem_flg 设置为 IPC_NOWAIT)实现,这会计算超时的值。

释放信号量

在 Windows 中,ReleaseSemaphore() 用来释放信号量。

BOOL ReleaseSemaphore(
  HANDLE hSemaphore,
  LONG lReleaseCount,
  LPLONG lpPreviousCount
);

在这段代码中:

  • hSemaphore 是一个指向信号量句柄的指针。
  • lReleaseCount 是信号量计数器,可以通过指定的数量来增加计数。
  • lpPreviousCount 是指向上一个信号量计数器返回时的变量的指针。如果并没有请求上一个信号量计数器的值,那么这个参数可以是 NULL。

这个函数会将信号量计数器的值增加在 lReleaseCount 中指定的值上,然后将这个信号量的状态设置为有信号状态。

在 Linux 中,我们使用 sem_post() 来释放信号量。这会唤醒对这个信号量进行阻塞的所有线程。信号量的计数器同时被增加 1。要为这个信号量的计数器添加指定的值(就像是 Windows 上一样),可以使用一个互斥变量多次调用以下函数:int sem_post(sem_t * sem)

对于 System V 信号量来说,只能使用 semop() 来释放信号量:int semop(int semid, struct sembuf *sops, unsigned nsops)

函数 semop() 原子地执行 sops 中包含的一组操作(只在所有操作都可以同时成功执行时,才会将所有的操作同时一次执行完)。sops 所指向的数组中的每个 nsops 元素都使用一个 struct sembuf 结构指定了一个要对这个信号量执行的操作,该结构包含以下元素:

  • unsigned short sem_num;(信号量个数)
  • short sem_op; (信号量操作)
  • short sem_flg; (操作标记)

要释放信号量,可以通过将 sem_op 设置为 1 来调用 semop()。通过将 semop() 设置为 1 来调用 semop(),这个信号量的计数器会增加 1,同时用信号通知这个信号量。

关闭/销毁信号量

在 Windows 中,我们使用 CloseHandle() 来关闭或销毁信号量对象。

BOOL CloseHandle(
  HANDLE hObject
);

hObject 是指向这个同步对象句柄的指针。

在 Linux 中,sem_destroy() 负责销毁信号量对象,并释放它所持有的资源: int sem_destroy(sem_t *sem)。对于 System V 信号量来说,只能使用 semctl() 函数的 IPC_RMID 命令来关闭信号量集:int semctl(int semid, int semnum, int cmd = IPC_RMID, ...)

这个命令将立即删除信号量集及其数据结构,并唤醒所有正在等待的进程(如果发生错误,则返回,并将 errno 设置为 EIDRM)。调用进程的有效用户 ID 必须是超级用户,或者可以与该信号量集的创建者或所有者匹配的用户。参数 semnum 会被忽略。

例子

下面是信号量的几个例子。


清单 1. Windows 无名信号量的代码

				
HANDLE hSemaphore;
LONG   lCountMax = 10;
LONG   lPrevCount;
DWORD  dwRetCode;
// Create a semaphore with initial and max. counts of 10.
hSemaphore = CreateSemaphore(
                      NULL,        // no security attributes
                      0,           // initial count
                      lCountMax,   // maximum count
                      NULL);       // unnamed semaphore
// Try to enter the semaphore gate.
dwRetCode = WaitForSingleObject(
                   hSemaphore,  // handle to semaphore
                   2000L);   // zero-second time-out interval
switch (dwRetCode)
{
    // The semaphore object was signaled.
    case WAIT_OBJECT_0:
        // Semaphore is signaled
        // go ahead and continue the work
        goto success:
        break;
    case WAIT_TIMEOUT:
        // Handle the time out case
        break;
}
Success:
// Job done, release the semaphore
ReleaseSemaphore(
        hSemaphore,  // handle to semaphore
        1,           // increase count by one
        NULL)        // not interested in previous count
// Close the semaphore handle
CloseHandle(hSemaphore);


清单 2. Linux 使用 POSIX 信号量的等效代码

				
				// Main thread
#define TIMEOUT 200  /* 2 secs */
// Thread 1
sem_t sem     ; // Global Variable
int   retCode ;
// Initialize event semaphore
retCode = sem_init(
                   sem,   // handle to the event semaphore
                   0,     // not shared
                   0);    // initially set to non signaled state
while (timeout < TIMEOUT ) {
   delay.tv_sec = 0;
   delay.tv_nsec = 1000000;  /* 1 milli sec */
   // Wait for the event be signaled
   retCode = sem_trywait(
                   &sem); // event semaphore handle
                          // non blocking call
   if (!retCode)  {
        /* Event is signaled */
        break;
   }
   else {
       /* check whether somebody else has the mutex */
       if (retCode == EPERM ) {
            /* sleep for delay time */
            nanosleep(&delay, NULL);
            timeout++ ;
       }
       else{
           /* error  */
       }
   }
}
// Completed the job,
// now destroy the event semaphore
retCode = sem_destroy(
                      &sem);   // Event semaphore handle
// Thread 2
// Condition met
// now signal the event semaphore
sem_post(
         &sem);    // Event semaphore Handle
 


清单 3. Linux 使用 System V 信号量的等效代码

				
				// Process 1
#define TIMEOUT 200
//Definition of variables
    key_t key;
    int semid;
    int Ret;
    int timeout = 0;
    struct sembuf operation[1] ;
    union semun
    {
        int val;
        struct semid_ds *buf;
        USHORT *array;
    } semctl_arg,ignored_argument;
    key = ftok(); // Generate a unique key, U can also supply a value instead
    semid = semget(key,             // a unique identifier to identify semaphore set
                    1,              // number of semaphore in the semaphore set
                   0666 | IPC_CREAT // permissions (rwxrwxrwx) on the new
                                    //    semaphore set and creation flag
                    );
   //Set Initial value for the resource
    semctl_arg.val = 0; //Setting semval to 0
    semctl(semid, 0, SETVAL, semctl_arg);
    //Wait for Zero
    while(timeout < TIMEOUT)
    {
        delay.tv_sec = 0;
        delay.tv_nsec = 1000000;  /* 1 milli sec */
        //Call Wait for Zero with IPC_NOWAIT option,so it will be non blocking
        operation[0].sem_op = -1; // Wait until the semaphore count becomes 0
        operation[0].sem_num = 0;
        operation[0].sem_flg = IPC_NOWAIT;
        ret = semop(semid, operation,1);
        if(ret < 0)
        {
            /* check whether somebody else has the mutex */
            if (retCode == EPERM )
            {
                /* sleep for delay time */
                nanosleep(&delay, NULL);
                timeout++ ;
            }
            else
            {
                printf("ERROR while wait ");
                break;
            }
        }
        else
        {
            /*semaphore got triggered */
            break;
        }
    }
    //Close semaphore
    iRc = semctl(semid, 1, IPC_RMID , ignored_argument);
}
// Process 2
key_t key = KEY; // Process 2 should know key value in order to open the
                 //    existing semaphore set
    struct sembuf operation[1] ;
    //Open semaphore
    semid = semget(key, 1, 0);
    operation[0].sem_op = 1; // Release the resource so Wait in process 1 will
                             //    be triggered
    operation[0].sem_num = 0;
    operation[0].sem_flg = SEM_UNDO;
    //Release semaphore
    semop(semid, operation,0);
}
 





回页首


事件

在 Windows 中,事件对象是那些需要使用 SetEvent() 函数显式地将其状态设置为有信号状态的同步对象。事件对象来源有两种类型:

  • 手工重置事件(manual reset event) 中,对象的状态会一直维持为有信号状态,直到使用 ResetEvent() 函数显式地重新设置它为止。
  • 自动重置事件(auto reset event) 中,对象的状态会一直维持为有信号状态,直到单个正在等待的线程被释放为止。当正在等待的线程被释放时,其状态就被设置为无信号的状态。

事件对象有两种状态,有信号(signaled)状态无信号(non-signaled)状态。对事件对象调用的等待函数会阻塞调用线程,直到其状态被设置为有信号状态为止。

在进行平台的迁移时,需要考虑以下问题:

  • Windows 提供了 有名(named)无名(un-named) 的事件对象。有名事件对象用来在进程之间进行同步,而在 Linux 中, pthreads 和 POSIX 都提供了线程间的同步功能。为了在 Linux 实现与 Windows 中有名事件对象相同的功能,可以使用 System V 信号量或信号。
  • Windows 提供了两种类型的事件对象 —— 手工重置对象和自动重置对象。Linux 只提供了自动重置事件的特性。
  • 在 Windows 中,事件对象的初始状态被设置为有信号状态。在 Linux 中,pthreads 并没有提供初始状态,而 POSIX 信号量则提供了一个初始状态。
  • Windows 事件对象是异步的。在 Linux 中,POSIX 信号量和 System V 信号量也都是异步的,不过 pthreads 条件变量不是异步的。
  • 当在一个等待函数中使用事件对象时,可以指定 Windows 的事件对象的超时时间值。在 Linux 中,只有 pthreads 在等待函数中提供了超时的特性。

还有几点非常重要,需要说明一下:

  • 尽管 POSIX 信号量是计数器信号量,但是当这个计数器被设置为 1 时,它们可以提供与 Windows 事件对象相似的功能。它们并不能在等待函数中提供超时时间。如果在进行移植时,超时并不是一个影响因素,那么建议您使用 POSIX 信号量。
  • 当与互斥一起使用时,pthreads 条件变量可以在线程之间提供基于事件的同步机制,不过这是同步的。根据应用程序的逻辑,这可以将此作为移植过程中在 Linux 上实现这种功能的一个选择。

表 3. 事件对象映射

Windows Linux 线程 Linux 进程 类别
CreateEvent
OpenEvent
pthread_cond_init
sem_init
semget
semctl
与上下文相关
SetEvent pthread_cond_signal
sem_post
semop 与上下文相关
ResetEvent N/A N/A 与上下文相关
WaitForSingleObject pthread_cond_wait
pthread_cond_timedwait
sem_wait
sem_trywait
semop 与上下文相关
CloseHandle pthread_cond_destroy
sem_destroy
semctl 与上下文相关

创建/打开事件对象

在 Windows 中,我们使用 CreateEvent() 来创建事件对象。

HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL bManualReset,
  BOOL bInitialState,
  LPCTSTR lpName
)

在这段代码中:

  • lpEventAttributes 是一个指针,它指向一个决定这个句柄是否能够被继承的属性。如果这个指针为 NULL,那么这个对象就不能被初始化。
  • bManualReset 是一个标记,如果该值为 TRUE,就会创建一个手工重置的事件,应该显式地调用 ResetEvent(),将事件对象的状态设置为无信号状态。
  • bInitialState 是这个事件对象的初始状态。如果该值为 true,那么这个事件对象的初始状态就被设置为有信号状态。
  • lpName 是指向这个事件对象名的指针。对于无名的事件对象来说,该值是 NULL。

这个函数创建一个手工重置或自动重置的事件对象,同时还要设置改对象的初始状态。这个函数返回事件对象的句柄,这样就可以在后续的调用中使用这个事件对象了。

OpenEvent() 用来打开一个现有的有名事件对象。这个函数返回该事件对象的句柄。

HANDLE OpenEvent(
  DWORD dwDesiredAccess,
  BOOL bInheritHandle,
  LPCTSTR lpName
)

在这段代码中:

  • dwDesiredAccess 是针对这个事件对象所请求的访问权。
  • bInheritHandle 是用来控制这个事件对象句柄是否可继承的标记。如果该值为 TRUE,那么这个句柄就可以被继承;否则就不能被继承。
  • lpName 是一个指向事件对象名的指针。

在 Linux 中,可以调用 sem_init() 来创建一个 POSIX 信号量:int sem_init(sem_t *sem, int pshared, unsigned int value)(其中 value(即信号量计数值)被设置为这个信号量的初始状态)。

Linux pthreads 使用 pthread_cond_init() 来创建一个条件变量:int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)

可以使用 PTHREAD_COND_INITIALIZER 常量静态地对 pthread_cond_t 类型的条件变量进行初始化,也可以使用 pthread_condattr_init() 对其进行初始化,这个函数会对与这个条件变量关联在一起的属性进行初始化。可以调用 pthread_condattr_destroy() 用来销毁属性:

int pthread_condattr_init(pthread_condattr_t *attr)
int pthread_condattr_destroy(pthread_condattr_t *attr)

等待某个事件

在 Windows 中,等待函数提供了获取同步对象的机制。我们可以使用不同类型的等待函数(此处我们只考虑 WaitForSingleObject())。这个函数会使用一个互斥对象的句柄,并一直等待,直到它变为有信号状态或超时为止。

DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD dwMilliseconds
);

在这段代码中:

  • hHandle 是指向互斥句柄的指针。
  • dwMilliseconds 是超时时间的值,单位是毫秒。如果该值为 INFINITE,那么它阻塞调用线程/进程的时间就是不确定的。

Linux POSIX 信号量使用 sem_wait() 来挂起调用线程,直到信号量的计数器变成非零的值为止。然后它会自动减小信号量计数器的值:int sem_wait(sem_t * sem)

在 POSIX 信号量中并没有提供超时操作。这可以通过在一个循环中执行非阻塞的 sem_trywait() 来实现,该函数会对超时时间进行计数:int sem_trywait(sem_t * sem).

Linux pthreads 使用 pthread_cond_wait() 来阻塞调用线程,其时间是不确定的:int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)。在另外一方面,如果调用线程需要被阻塞一段确定的时间,那么就可以使用 pthread_cond_timedwait() 来阻塞这个线程。如果在这段指定的时间内条件变量并没有出现,那么 pthread_cond_timedwait() 就会返回一个错误:int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,const struct timespec *abstime)。在这里,abstime 参数指定了一个绝对时间(具体来说,就是从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在所经过的时间。)

改变事件对象的状态

函数 SetEvent() 用来将事件对象的状态设置为有信号状态。对一个已经设置为有信号状态的事件对象再次执行该函数是无效的。

BOOL SetEvent(
  HANDLE hEvent
)

Linux POSIX 信号量使用 sem_post() 来发出一个事件信号量。这会唤醒在该信号量上阻塞的所有线程:int sem_post(sem_t * sem)

调用 pthread_cond_signal() 被用在 LinuxThreads 中,以唤醒在某个条件变量上等待的一个线程,而 pthread_cond_broadcast() 用来唤醒在某个条件变量上等待的所有线程。

int pthread_cond_signal(pthread_cond_t *cond)
int pthread_cond_broadcast(pthread_cond_t *cond)

注意,条件函数并不是异步信号安全的,因此不能在信号处理函数中调用。具体地说,在信号处理函数中调用 pthread_cond_signal()pthread_cond_broadcast() 可能会导致调用线程的死锁。

重置事件的状态

在 Windows 中,ResetEvent() 用来将事件对象的状态重新设置为无信号状态。

BOOL ResetEvent(
  HANDLE hEvent
);

在 Linux 中,条件变量和 POSIX 信号量都是自动重置类型的。

关闭/销毁事件对象

在 Windows 中,CloseHandle() 用来关闭或销毁事件对象。

BOOL CloseHandle(
  HANDLE hObject
);

在这段代码中,hObject 是指向同步对象句柄的指针。

在 Linux 中, sem_destroy()/ pthread_cond_destroy() 用来销毁信号量对象或条件变量,并释放它们所持有的资源:

int sem_destroy(sem_t *sem)
int pthread_cond_destroy(pthread_cond_t *cond)

有名事件对象

在 Linux 中,进程之间有名事件对象所实现的功能可以使用 System V 信号量实现。System V 信号量是计数器变量,因此可以实现 Windows 中事件对象的功能,信号量的计数器的初始值可以使用 semctl() 设置为 0。

要将某个事件的状态修改为有信号状态,可以使用 semop(),并将 sem_op 的值设置为 1。要等待某个事件,则可以使用 semop() 函数,并将 sem_op 的值设置为 -1,这样就可以阻塞调用进程,直到它变为有信号状态为止。

可以通过使用 semctl() 将信号量计数器的初始值设置为 0 来获得信号量。在使用完共享资源之后,可以使用 semop() 将信号量计数设置为 1。关于每个 System V 信号量的原型,请参阅本文中有关信号量一节的内容。

例子

下面几个例子可以帮助您理解我们在这一节中所讨论的内容。


清单 4. Windows 无名事件对象的代码

				
				// Main thread
HANDLE hEvent; // Global Variable
// Thread 1
DWORD  dwRetCode;
// Create Event
hEvent = CreateEvent(
                     NULL,    // no security attributes
                     FALSE,   // Auto reset event
                     FALSE,   // initially set to non signaled state
                     NULL);   // un named event
// Wait for the event be signaled
dwRetCode = WaitForSingleObject(
                                hEvent,    // Mutex handle
                              INFINITE);   // Infinite wait
switch(dwRetCode) {
          case WAIT_OBJECT_O :
                 // Event is signaled
                 // go ahead and proceed the work
         default :
                   // Probe for error
}
// Completed the job,
// now close the event handle
CloseHandle(hEvent);
// Thread 2
// Condition met for the event hEvent
// now set the event
SetEvent(
         hEvent);    // Event Handle


清单 5. Linux 使用 POSIX 信号量的等效代码

				
				// Main thread
sem_t sem     ; // Global Variable
// Thread 1
int   retCode ;
// Initialize event semaphore
retCode = sem_init(
                   sem,   // handle to the event semaphore
                   0,     // not shared
                   0);    // initially set to non signaled state
// Wait for the event be signaled
retCode = sem_wait(
                   &sem); // event semaphore handle
                          // Indefinite wait
// Event Signaled
// a head and proceed the work
// Completed the job,
// now destroy the event semaphore
retCode = sem_destroy(
                      &sem);   // Event semaphore handle
// Thread 2
// Condition met
// now signal the event semaphore
sem_post(
         &sem);    // Event semaphore Handle


清单 6. Linux 中使用条件变量的等效代码

				
				// Main thread
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;
// Thread 1
 ...
pthread_mutex_lock(&mutex);
// signal one thread to wake up
pthread_cond_signal(&condvar);
pthread_mutex_unlock(&mutex);
// this signal is lost as no one is waiting
// Thread 1 now tries to take the mutex lock
// to send the signal but gets blocked
     ...
    pthread_mutex_lock(&mutex);
// Thread 1 now gets the lock and can
// signal thread 2 to wake up
      pthread_cond_signal(&condvar);
      pthread_mutex_unlock(&mutex);
// Thread 2
pthread_mutex_lock(&mutex);
pthread_cond_wait(&condvar, &mutex);
pthread_mutex_unlock(&mutex);
// Thread 2 blocks indefinitely
// One way of avoiding losing the signal is as follows
// In Thread 2 - Lock the mutex early to avoid losing signal
pthread_mutex_lock (&mutex);
// Do work
.......
// This work may lead other threads to send signal to thread 2
// Thread 2 waits for indefinitely for the signal to be posted
pthread_cond_wait (&condvar, &Mutex );
// Thread 2 unblocks upon receipt of signal
pthread_mutex_unlock (&mutex);


清单 7. Windows 中使用有名事件的例子

				
				// Process 1
DWORD  dwRetCode;
HANDLE hEvent; // Local variable
// Create Event
hEvent = CreateEvent(
                     NULL,        // no security attributes
                     FALSE,       // Auto reset event
                     FALSE,       // initially set to non signaled state
                     "myEvent");  // un named event
// Wait for the event be signaled
dwRetCode = WaitForSingleObject(
                                hEvent,    // Mutex handle
                              INFINITE);   // Infinite wait
switch(dwRetCode) {
          case WAIT_OBJECT_O :
                 // Event is signaled
                 // go ahead and proceed the work
         default :
                   // Probe for error
}
// Completed the job,
// now close the event handle
CloseHandle(hEvent);
// Process 2
HANDLE hEvent; // Local variable
// Open the Event
hEvent = CreateEvent(
                     NULL,        // no security attributes
                     FALSE,       // do not inherit handle
                     "myEvent");  // un named event
// Condition met for the event hEvent
// now set the event
SetEvent(
         hEvent);    // Event Handle
// completed the job, now close the event handle
CloseHandle(hEvent);


清单 8. Linux 中使用 System V 信号量的等效代码

				
				// Process 1
int main()
{
    //Definition of variables
    key_t key;
    int semid;
    int Ret;
    int timeout = 0;
    struct sembuf operation[1] ;
    union semun
    {
        int val;
        struct semid_ds *buf;
        USHORT *array;
    } semctl_arg,ignored_argument;
    key = ftok(); /Generate a unique key, U can also supply a value instead
    semid = semget(key,                // a unique identifier to identify semaphore set
                     1,                // number of semaphore in the semaphore set
                     0666 | IPC_CREAT  // permissions (rwxrwxrwx) on the new
                                       //     semaphore set and creation flag
                    );
    if(semid < 0)
    {
        printf("Create semaphore set failed ");
        Exit(1);
    }
    //Set Initial value for the resource - initially not owned
    semctl_arg.val = 0; //Setting semval to 0
    semctl(semid, 0, SETVAL, semctl_arg);
    // wait on the semaphore
    // blocked until it is signaled
    operation[0].sem_op = -1;
    operation[0].sem_num = 0;
    operation[0].sem_flg = IPC_WAIT;
    ret = semop(semid, operation,1);
    // access the shared resource
    ...
    ...
    //Close semaphore
    iRc = semctl(semid, 1, IPC_RMID , ignored_argument);
}
// Process 2
int main()
{
    key_t key = KEY; //Process 2 shd know key value in order to open the
                // existing semaphore set
    struct sembuf operation[1] ;
    //Open semaphore
    semid = semget(key, 1, 0);
    // signal the semaphore by incrementing the semaphore count
    operation[0].sem_op = 1;
    operation[0].sem_num = 0;
    operation[0].sem_flg = SEM_UNDO;
    semop(semid, operation,0);
}
 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值