`
cuiyadll
  • 浏览: 204634 次
文章分类
社区版块
存档分类
最新评论

将 Win32 C/C++ 应用程序迁移到 POWER 上的 Linux,第 2 部分: 互斥

阅读更多

 http://blog.csdn.net/j6915819/article/details/17219863

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

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

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

  • 在 第 1 部分 中,我们已经对进程和线程进行了讨论。
  • 本文将介绍信号量和事件。
  • 第 3 部分将介绍互斥、临界区和等待函数。

在本文中,我们将从同步技术入手,继续从 Windows 到 Linux 的映射指导。

同步

在 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.cuid 和 sem_perm.uid 被设置为调用进程的有效用户 ID。
  • sem_perm.cgid 和 sem_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_NOWAIT 和 SEM_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);
}
 

 
分享到:
评论

相关推荐

    c/c++完整视频教程(三)——–Linux服务器编程

    根据提供的文件信息,本篇文章将围绕“C/C++完整视频教程(三)——Linux服务器编程”这一主题展开,深入解析该教程所涵盖的关键知识点。由于实际视频内容无法直接获取,以下解析将基于标题、描述及标签提供的信息...

    c/c++ 聊天程序

    本资源提供的"聊天程序 c/C++"是学习这些技术的一个经典实例,旨在帮助开发者理解如何利用C/C++实现一个基本的聊天应用程序。 1. **网络编程**:在C/C++中,实现聊天程序的核心在于网络通信。通常,我们会用到套接...

    Borland C/C++ 3.1 完整版(BC3.1)

    【Borland C/C++ 3.1 完整版(BC3.1)】是一款经典的集成开发环境(IDE),由Borland公司发布,专为编写16位应用程序而设计。这款编译器在20世纪90年代初期非常流行,尤其在DOS和Windows 3.x时代,是许多开发者首选的C...

    linux 应用软件 c/c++ 面试题

    在Linux环境下进行C/C++应用软件的开发,面试时可能会涉及到多个方面的问题,这些知识点涵盖了操作系统原理、编程语言特性、软件工程以及系统级编程等多个领域。以下是一些可能的面试重点: 1. **C/C++语言基础**:...

    C/C++程序员面试宝典

    C语言以其简洁高效著称,而C++则在此基础上增加了面向对象的特性,极大地扩展了其功能和应用范围。 面试中,面试官通常会考察以下几个方面的知识: 1. **基础语法**:包括变量、数据类型、运算符、流程控制(如if-...

    嵌入式c/c++编程

    嵌入式系统编程是计算机科学的一个重要分支,它涉及到硬件和软件的紧密集成,用于创建具有特定功能的设备或系统。在嵌入式系统中,C和C++语言因其高效、灵活和对底层硬件控制的能力而被广泛使用。以下是关于"嵌入式C...

    linux和win32下通用的互斥锁Mutex封装

    3. `mutex_win32.cpp`:实现了Mutex类在Windows环境下的逻辑,可能使用了CreateMutex或InitializeCriticalSection等API。 4. `test_mutex.cpp`:测试程序,展示了如何在实际应用中使用Mutex类。 使用这个封装库时,...

    Windows Via C/C++

    2. **进程与线程**: 进程和线程是操作系统中的基本概念,书中会讲解如何在C/C++中创建、管理和同步进程及线程,包括同步原语如互斥量、事件对象和信号量。 3. **内存管理**: 在Windows平台上,理解内存分配、释放...

    Windows下的c/c++高级编程 源代码

    《Windows下的C/C++高级编程》是一本专为在Windows平台上进行C/C++高级开发而设计的书籍。作者朱磊和周彬通过丰富的实践经验,详细介绍了如何利用C/C++语言在Windows环境中进行系统级编程、底层开发以及高效能应用的...

    Linux下互斥锁的应用

    Linux下互斥锁的应用,用作互斥锁入门

    C/C++程序设计导论(第二版)附书源码

    - **起源**:C++是由Bjarne Stroustrup在1983年基于C语言开发的,最初名为“C with Classes”。 - **特性**: - 面向对象:支持类与对象的概念,有助于构建复杂的应用程序。 - 泛型编程:通过模板支持泛型编程,...

    linux C/C++超有用4

    在Linux环境下进行C/C++编程是一项重要...以上只是Linux环境下C/C++编程的一部分核心概念,实际开发中还会涉及到网络编程、信号处理、系统调用、性能分析等诸多方面。熟练掌握这些知识,将使你在Linux开发中游刃有余。

    linux 串口类(c/c++)

    根据提供的文件信息,我们可以归纳出以下关键知识点,这些知识点主要围绕着Linux环境下串口设备的C/C++编程。 ### 1. Linux串口编程简介 #### 1.1 串口设备的应用背景 - **工控领域**:工业控制系统经常需要通过...

    win32最基础应用程序

    Win32最基础应用程序是指基于Microsoft Windows操作系统的32位应用程序开发。Win32 API(应用程序接口)是Windows操作系统提供给开发者的一个接口,允许程序员直接与操作系统交互,创建各种功能丰富的桌面应用程序。...

    Win32 / Linux 操作系统测试程序

    针对“Win32 / Linux 操作系统测试程序”,我们将深入探讨这两个不同操作系统的内核实现,以及如何通过多线程测试源码进行系统级别的调试与分析。 首先,Win32指的是微软Windows操作系统的一个API(应用程序接口)...

    高质量编程C/C++(第三版)

    《高质量编程C/C++(第三版)》是针对C++和C语言编程的一本权威指南,旨在提升程序员的代码质量和专业素养。这本书不仅涵盖了编程的基本规范,还特别关注了在面试过程中可能会遇到的问题,帮助读者在技术面试中...

    演示了在WIN32下实现程序互斥运行的方法(24kb)

    在Windows操作系统环境下,有时我们需要确保一个应用程序只能有一个实例在运行,这种技术被称为程序互斥运行。这通常通过创建全局共享资源来实现,如全局互斥体(Mutex)。本示例"演示了在WIN32下实现程序互斥运行的...

    腾讯c/c++面试题

    2. **树的序列化**:题目要求将树转换为数组或链表的形式,这涉及到树的遍历(前序、中序、后序或层次遍历)和数据结构之间的转换,是数据结构知识的重要组成部分。 3. **二分查找与快速排序**:这些是经典的排序和...

    linux C/C++ 数据库连接池

    在Linux环境下,C/C++开发数据库连接池是提高应用程序性能和效率的重要技术。数据库连接池是一种管理数据库连接的机制,它允许程序重复使用已建立的数据库连接,而不是每次需要时都创建新的连接。这减少了创建和销毁...

    C/C++几道笔试题

    6. **异常处理**:C++的异常处理机制是处理错误的重要方式,面试题可能会涉及到try-catch语句的使用,以及异常的抛出和捕获。 7. **STL(标准模板库)**:包括容器(如vector, list, set, map等)、算法和迭代器。...

Global site tag (gtag.js) - Google Analytics