将您的 Win32 C/C++ 应用程序迁移到 POWER? 上的 Linux?,并从信号(semaphore)应用程序接口(application program interface,API)的角度理解 Win32 到 Linux 的映射。Nam Keung 将通过详细的代码示例来为您描述这一过程。
介绍
本系列第三篇文章从信号的角度阐述了 Win32 C/++ 应用程序向 POWER 上的 Linux 的迁移。本系列的第 1 部分介绍了 Win32 API 映射,第 2 部分从互斥(mutex)API 的角度集中阐述了如何将 Win32 映射到 Linux。在继续阅读之前,建议您先去阅读本系列的第 1 部分和第 2 部分。
信号
信号是包含有一个正数的资源。信号允许进程通过一个单一的原子操作来测试和设置那个整数的值,以此实现同步。通常,信号的主要用途是同步某个线程与其他线程的动作。在多个进程竞争访问同一操作系统资源时,这也是协调或者同步那些行为的一种实用技术。
Linux 支持 Portable Operating System Interface(POSIX)信号以及 pthread 条件变量,以此来映射 Win32 信号 API。它们各有其优缺点。您应该基于应用程序的逻辑来判断使用哪种方法。在映射事件信号的过程中需要考虑的方面包括:
- 信号的类型:Win32 既支持有名称的事件信号,也支持无名称的事件信号。有名称的信号是在多个进程间共享的。Linux 不支持这种方案。本文中列出的一个进程间通信(Inter-Process Communication,IPC)消息队列示例代码将向您展示如何来解决此问题。
- 初始状态:在 Win32 中,信号可能会有初始值。在 Linux 中,POSIX 信号支持此功能,但 pthreads 不支持。在使用 pthreads 时您需要考虑到这一点。
- 超时:Win32 事件信号支持定时等待。在 Linux 中,POSIX 信号实现只支持不确定的等待(阻塞)。pthreads 实现既支持阻塞也支持超时。
pthread_cond_timedwait()
调用能给出等待期间的超时的值,pthread_cond_wait()
则用于不确定的等待。 - 发信号:在 Win32 中,发出信号会唤醒等待那个信号的所有线程。在 Linux 中,POSIX 线程实现一次只唤醒一个线程。pthreads 实现的
pthread_cond_signal()
调用会唤醒一个线程,pthread_cond_broadcast()
调用会向所有等待那个信号的线程发出信号。
表 1. 信号映射表
Win32 | pthread Linux | POSIX |
CreateSemaphore | pthread_mutex_init(&(token)->mutex, NULL)) | sem_init |
CloseHandle (semHandle) | pthread_mutex_destroy(&(token->mutex)) | sem_destroy |
ReleaseSemaphore(semHandle, 1, NULL) | pthread_cond_signal(&(token->condition)) | sem_post |
WaitForSingleObject(semHandle, | pthread_cond_wait(&(token->condition), | sem_wait |
条件变量
条件变量让开发者能够实现一个条件,在这个条件下线程执行然后被阻塞。Microsoft? Win32 接口本身不支持条件变量。为解决此缺憾,我使用 POSIX 条件变量模拟同步原语,并在一系列文章中对此进行了概述。在 Linux 中,它可以确保因某条件被阻塞的线程,当那个条件改变时,会被解除阻塞。它还允许您原子地(atomically)解除互斥的锁定,并等待条件变量,而不会有干涉其他线程的可能。不过,每个条件变量都应该伴有一个互斥。前面的表 1 给出了用于线程间同步的 pthread 条件变量。
创建信号
在 Win32 中,CreateSemaphore
函数可以创建一个有名称的或者无名称的信号对象。Linux 不支持有名称的信号。
清单 1. 创建信号
HANDLE CreateSemaphore (
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximunCount,
LPCTSTR lpName
);
|
在 Linux 中,sem_init()
调用会创建一个 POSIX 信号:
清单 2. POSIX 信号
int sem_init(sem_t *sem, int pshared, unsigned int value
|
Linux 使用 pthread_condition_init
调用在当前进程内创建信号对象,在其中维持一个在零与最大值之间的计数值。每次有某个线程完成对信号的等待,这个计数值会减小,而每次当某个线程释放这个信号时,计数值增加。当计数值成为零时,信号对象的状态成为 non-signaled。
清单 3. 创建信号对象的 pthread_condition_init
调用
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
|
清单 4. Win32 示例代码
HANDLE semHandle;
semHandle = CreateSemaphore(NULL, 0, 256000, NULL);
/* Default security descriptor */
if( semHandle == (HANDLE) NULL)
/* Semaphore object without a name */
{
return RC_OBJECT_NOT_CREATED;
}
|
清单 5. 相应的 Linux 代码
typedef struct
{
pthread_mutex_t mutex;
pthread_cond_t condition;
int semCount;
}sem_private_struct, *sem_private;
sem_private token;
token = (sem_private) malloc(sizeof(sem_private_struct));
if(rc = pthread_mutex_init(&(token->mutex), NULL))
{
free(token);
return RC_OBJECT_NOT_CREATED;
}
if(rc = pthread_cond_init(&(token->condition), NULL))
{
pthread_mutex_destroy( &(token->mutex) );
free(token);
return RC_OBJECT_NOT_CREATED;
}
token->semCount = 0;
|
销毁事件信号
Win32 使用 CloseHandle
来删除由 CreateSemaphore
所创建的信号对象。
清单 6. 销毁事件信号
BOOL CloseHandle (HANDLE hObject);
|
Linux POSIX 信号使用 sem_destroy()
来销毁无名称的信号。
清单 7.
sem_destroy()
int sem_destroy(sem_t *sem); |
在 Linux pthreads 中,使用 pthread_cond_destroy()
来销毁条件变量。
清单 8. pthread_cond_destroy()
int pthread_cond_destroy(pthread_cond_t *cond); |
清单 9. Win32 代码和相应的 Linux 代码
Win32 代码 | 相应的 Linux 代码 |
CloseHandle(semHandle); | pthread_mutex_destroy(&(token->mutex)); |
发布事件信号
在 Win32 中,ReleaseSemaphore
函数会令指定的信号对象的计数值增加指定数量。
清单 10. ReleaseSemaphore
函数
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lpPreviousCount
);
|
Linux POSIX 信号使用 sem_post()
来发布事件信号。这将唤醒阻塞于此信号的所有线程。
清单 11. sem_post()
int sem_post(sem_t * sem);
|
在 Linux 中,pthread_cond_signal
会唤醒等待某个条件变更的某个线程。Linux 调用这个函数来为此对象所标识的信号发布一个事件完成信号。调用的线程增加那个信号的值。如果信号的值从零开始增加,而且 pthread_cond
中有任何线程被阻塞,那么请等待这个信号,因为其中一个会被唤醒。默认情况下,实现可以选择任意的正在等待的线程。
清单 12. pthread_cond_signal
int pthread_cond_signal(pthread_cond_t *cond);
|
清单 13. Win32 代码和相应的 Linux 代码
Win32 代码 | 相应的 Linux 代码 |
ReleaseSemaphore(semHandle, 1, NULL) | if (rc = pthread_mutex_lock(&(token->mutex))) |
等待事件信号
Win32 调用 WaitForSingleObject
函数来等待所需要的信号上事件的完成。当等待单一线程同步对象时,可以使用此方法。当对象被设置发出信号或者超时时间段结束时,这个方法会得到通知。如果时间间隔是 INFINITE,那么它就会无止境地等待下去。
清单 14. WaitForSingleObject
函数
DWORD WaitForSingleObject(
HANDLE hHANDLE,
DWORD dwMilliseconds
);
|
使用 WaitForMultipleObjects
函数来等待多个被通知的对象。在信号线程同步对象中,当计数器变为零时,对象是 non-signaled。
清单 15. WaitForMultipleObjects
函数
DWORD WaitForMultipleObjects(
DWORD nCount,
Const HANDLE* lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds
);
|
Linux POSIX 信号使用 sem_wait()
来挂起发出调用的线程,直到信号拥有了非零的计数值。然后它自动地减少信号的计数值。
清单 16. sem_wait()
函数
int sem_wait(sem_t * sem);
|
在 POSIX 信号中不能使用超时选项。不过,您可以通过在某个循环中执行非阻塞的 sem_trywait()
来完成此功能,它会计算超时的值。
清单 17. sem_trywait()
函数
int sem_trywait(sem_t * sem);
|
在 Linux 中,pthread_cond_wait()
会阻塞发出调用的线程。发出调用的线程会减小那个信号。如果当 pthread_cond_wait
被调用时信号是零,则 pthread_cond_wait()
就会阻塞,直到另一个线程增加了那个信号的值。
清单 18. pthread_cond_wait()
函数
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
|
pthread_cond_wait
函数首先释放相关联的 external_mutex of type pthread_mutex_t
,当调用者检查条件表达式时必须持有它。
清单 19. Win32 代码和相应的 Linux 代码
Win32 代码 | 相应的 Linux 代码 |
DWORD retVal; | if (rc = pthread_mutex_lock(&(token->mutex))) |
如果您需要在指定的一段时间内阻塞发出调用的线程,那么请使用 pthread_cond_timewait
来阻塞它。调用这个方法来等待所需要信号上某个事件的完成,等待指定的一段时间。
清单 20. pthread_cond_timewait
int pthread_cond_timewait(
pthread_cond_t *cond,
pthread_mutex_t *mutex,
timespec *tm
); |
清单 21. Win32 代码和相应的 Linux 代码
Win32 代码 | 相应的 Linux 代码 |
retVal = WaitForSingleObject(SemHandle, timelimit); | int rc; |
POSIX 信号示例代码
清单 22 使用 POSIX 信号来实现线程 A 和 B 之间的同步:
清单 22. POSIX 信号示例代码
sem_t sem; /* semaphore object */
int irc; /* return code */
/* Initialize the semaphore - count is set to 1*/
irc = sem_init (sem, 0,1)
...
/* In Thread A */
/* Wait for event to be posted */
sem_wait (&sem);
/* Unblocks immediately as semaphore initial count was set to 1 */
.......
/* Wait again for event to be posted */
sem_wait (&sem);
/* Blocks till event is posted */
/* In Thread B */
/* Post the semaphore */
...
irc = sem_post (&sem);
/* Destroy the semaphore */
irc = sem_destroy(&sem);
|
进程内信号示例代码
清单 23. Win32 进程内信号示例代码
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
void thrdproc (void *data); // the thread procedure (function)
to be executed
HANDLE semHandle;
int
main( int argc, char **argv )
{
HANDLE *threadId1;
HANDLE *threadId2;
int hThrd;
unsigned stacksize;
int arg1;
if( argc < 2 )
arg1 = 7;
else
arg1 = atoi( argv[1] );
printf( "Intra Process Semaphor test.\n" );
printf( "Start.\n" );
semHandle = CreateSemaphore(NULL, 1, 65536, NULL);
if( semHandle == (HANDLE) NULL)
{
printf("CreateSemaphore error: %d\n", GetLastError());
}
printf( "Semaphor created.\n" );
if( stacksize < 8192 )
stacksize = 8192;
else
stacksize = (stacksize/4096+1)*4096;
hThrd = _beginthread( thrdproc, // Definition of a thread entry
NULL,
stacksize,
"Thread 1");
if (hThrd == -1)
return RC_THREAD_NOT_CREATED);
*threadId1 = (HANDLE) hThrd;
hThrd = _beginthread( thrdproc, // Definition of a thread entry
NULL,
stacksize,
“Thread 2");
if (hThrd == -1)
return RC_THREAD_NOT_CREATED);
*threadId2 = (HANDLE) hThrd;
printf( "Main thread sleeps 5 sec.\n" );
sleep(5);
if( ! ReleaseSemaphore(semHandle, 1, NULL) )
printf("ReleaseSemaphore error: %d\n", GetLastError());
printf( "Semaphor released.\n" );
printf( "Main thread sleeps %d sec.\n", arg1 );
sleep (arg1);
if( ! ReleaseSemaphore(semHandle, 1, NULL) )
printf("ReleaseSemaphore error: %d\n", GetLastError());
printf( "Semaphor released.\n" );
printf( "Main thread sleeps %d sec.\n", arg1 );
sleep (arg1);
CloseHandle(semHandle);
printf( "Semaphor deleted.\n" );
printf( "Main thread sleeps 5 sec.\n" );
sleep (5);
printf( "Stop.\n" );
return OK;
}
void
thread_proc( void *pParam )
{
DWORD retVal;
printf( "\t%s created.\n", pParam );
retVal = WaitForSingleObject(semHandle, INFINITE);
if (retVal == WAIT_FAILED)
return RC_SEM_WAIT_ERROR;
printf( "\tSemaphor blocked by %s. (%lx)\n", pParam, retVal);
printf( "\t%s sleeps for 5 sec.\n", pParam );
sleep(5);
if( ! ReleaseSemaphore(semHandle, 1, NULL) )
printf("ReleaseSemaphore error: %d\n", GetLastError());
printf( "\tSemaphor released by %s.)\n", pParam);
}
|
清单 24. 相应的 Linux 进程内信号示例代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
void thread_proc (void * data);
pthread_mutexattr_t attr;
pthread_mutex_t mutex;
typedef struct
{
pthread_mutex_t mutex;
pthread_cond_t condition;
int semCount;
}sem_private_struct, *sem_private;
sem_private token;
int main( int argc, char **argv )
{
pthread_t threadId1;
pthread_t threadId2;
pthread_attr_t pthread_attr;
pthread_attr_t pthread_attr2;
int arg1;
int rc;
if( argc < 2 )
arg1 = 7;
else
arg1 = atoi( argv[1] );
printf( "Intra Process Semaphor test.\n" );
printf( "Start.\n" );
token =(sem_private) malloc (sizeof (sem_private_struct));
if(rc = pthread_mutex_init( &(token->mutex), NULL))
{
free(token);
return 1;
}
if(rc = pthread_cond_init(&(token->condition), NULL))
{
printf( "pthread_condition ERROR.\n" );
pthread_mutex_destroy( &(token->mutex) );
free(token);
return 1;
}
token->semCount = 0;
printf( "Semaphor created.\n" );
if (rc = pthread_attr_init(&pthread_attr))
{
printf( "pthread_attr_init ERROR.\n" );
exit;
}
if (rc = pthread_attr_setstacksize(&pthread_attr, 120*1024))
{
printf( "pthread_attr_setstacksize ERROR.\n" );
exit;
}
if (rc = pthread_create(&threadId1,
&pthread_attr,
(void*(*)(void*))thread_proc,
"Thread 1" ))
{
printf( "pthread_create ERROR.\n" );
exit;
}
if (rc = pthread_attr_init(&pthread_attr2))
{
printf( "pthread_attr_init2 ERROR.\n" );
exit;
}
if (rc = pthread_attr_setstacksize(&pthread_attr2, 120*1024))
{
printf( "pthread_attr_setstacksize2 ERROR.\n" );
exit;
}
if (rc = pthread_create(&threadId2,
&pthread_attr2,
(void*(*)(void*))thread_proc,
"Thread 2" ))
{
printf( "pthread_CREATE ERROR2.\n" );
exit ; // EINVAL, ENOMEM
}
printf( "Main thread sleeps 5 sec.\n" );
sleep( 5 );
if (rc = pthread_mutex_lock(&(token->mutex)))
{
printf( "pthread_mutex_lock ERROR 1.\n" );
return 1;
}
token->semCount ++;
if (rc = pthread_mutex_unlock&(token->mutex)))
{
printf( "pthread_mutex_unlock ERROR 1.\n" );
return 1;
}
if (rc = pthread_cond_signal(&(token->condition)))
{
printf( "pthread_cond_signal ERROR1.\n" );
return 1;
}
printf( "Semaphor released.\n" );
printf( "Main thread sleeps %d sec.\n", arg1 );
sleep( arg1 );
if (rc = pthread_mutex_lock(&(token->mutex)))
{
printf( "pthread_mutex_lock ERROR.\n" );
return 1;
}
token->semCount ++;
if (rc = pthread_mutex_unlock(&(token->mutex)))
{
printf( "pthread_mutex_lock ERROR.\n" );
return 1;
}
if (rc = pthread_cond_signal(&(token->condition)))
{
printf( "pthread_cond_signal ERROR.\n" );
return 1;
}
printf( "Semaphor released.\n" );
printf( "Main thread sleeps %d sec.\n", arg1 );
sleep( arg1 );
pthread_mutex_destroy(&(token->mutex));
pthread_cond_destroy(&(token->condition));
printf( "Semaphor deleted.\n" );
printf( "Main thread sleeps 5 sec.\n" );
sleep( 5 );
printf( "Stop.\n" );
return 0;
}
void
thread_proc( void *pParam )
{
int rc;
printf( "\t%s created.\n", pParam );
if (token == (sem_private) NULL)
return ;
if (rc = pthread_mutex_lock(&(token->mutex)))
{
printf( "pthread_mutex_lock ERROR2.\n" );
return ;
}
while (token->semCount <= 0)
{
rc = pthread_cond_wait(&(token->condition), &(token->mutex));
if (rc && errno != EINTR )
break;
}
if( rc )
{
pthread_mutex_unlock(&(token->mutex));
printf( "pthread_mutex_unlock ERROR3.\n" );
return;
}
token->semCount--;
if (rc = pthread_mutex_unlock(&(token->mutex)))
{
printf( "pthread_mutex_lock ERROR.\n" );
return ;
}
printf( "\tSemaphor blocked by %s. (%lx)\n", pParam, rc );
printf( "\t%s sleeps for 5 sec.\n", pParam );
sleep( 5 );
if (rc = pthread_mutex_lock(&(token->mutex)))
{
printf( "pthread_mutex_lock ERROR.\n" );
return ;
}
token->semCount ++;
if (rc = pthread_mutex_unlock(&(token->mutex)))
{
printf( "pthread_mutex_unlock ERROR.\n" );
return ;
}
if (rc = pthread_cond_signal(&(token->condition)))
{
printf( "pthread_cond_signal ERROR.\n" );
return ;
}
printf( "\tSemaphor released by %s. (%lx)\n", pParam, rc );
|