信号
信号是包含有一个正数的资源。信号允许进程通过一个单一的原子操作来测试和设置那个整数的值,以此实现同步。通常,信号的主要用途是同步某个线程与其他线程的动作。在多个进程竞争访问同一操作系统资源时,这也是协调或者同步那些行为的一种实用技术。
Linux 支持 Portable Operating System Interface(POSIX)信号以及 pthread 条件变量,以此来映射 Win32 信号 API。它们各有其优缺点。您应该基于应用程序的逻辑来判断使用哪种方法。在映射事件信号的过程中需要考虑的方面包括:
pthread_cond_timedwait()
调用能给出等待期间的超时的值,pthread_cond_wait()
则用于不确定的等待。pthread_cond_signal()
调用会唤醒一个线程,pthread_cond_broadcast()
调用会向所有等待那个信号的线程发出信号。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 不支持有名称的信号。
HANDLE CreateSemaphore ( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximunCount, LPCTSTR lpName ); |
在 Linux 中,sem_init()
调用会创建一个 POSIX 信号:
int sem_init(sem_t *sem, int pshared, unsigned int value |
Linux 使用 pthread_condition_init
调用在当前进程内创建信号对象,在其中维持一个在零与最大值之间的计数值。每次有某个线程完成对信号的等待,这个计数值会减小,而每次当某个线程释放这个信号时,计数值增加。当计数值成为零时,信号对象的状态成为 non-signaled。
pthread_condition_init
调用
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); |
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; } |
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
所创建的信号对象。
BOOL CloseHandle (HANDLE hObject); |
Linux POSIX 信号使用 sem_destroy()
来销毁无名称的信号。
sem_destroy()
int sem_destroy(sem_t *sem); |
在 Linux pthreads 中,使用 pthread_cond_destroy()
来销毁条件变量。
pthread_cond_destroy()
int pthread_cond_destroy(pthread_cond_t *cond); |
Win32 代码 | 相应的 Linux 代码 |
CloseHandle(semHandle); |
pthread_mutex_destroy(&(token->mutex)); |
回页首
发布事件信号
在 Win32 中,ReleaseSemaphore
函数会令指定的信号对象的计数值增加指定数量。
ReleaseSemaphore
函数
BOOL ReleaseSemaphore( HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount ); |
Linux POSIX 信号使用 sem_post()
来发布事件信号。这将唤醒阻塞于此信号的所有线程。
sem_post()
int sem_post(sem_t * sem); |
在 Linux 中,pthread_cond_signal
会唤醒等待某个条件变更的某个线程。Linux 调用这个函数来为此对象所标识的信号发布一个事件完成信号。调用的线程增加那个信号的值。如果信号的值从零开始增加,而且 pthread_cond
中有任何线程被阻塞,那么请等待这个信号,因为其中一个会被唤醒。默认情况下,实现可以选择任意的正在等待的线程。
pthread_cond_signal
int pthread_cond_signal(pthread_cond_t *cond); |
Win32 代码 | 相应的 Linux 代码 |
ReleaseSemaphore(semHandle, 1, NULL) |
if (rc = pthread_mutex_lock(&(token->mutex))) |
回页首
等待事件信号
Win32 调用 WaitForSingleObject
函数来等待所需要的信号上事件的完成。当等待单一线程同步对象时,可以使用此方法。当对象被设置发出信号或者超时时间段结束时,这个方法会得到通知。如果时间间隔是 INFINITE,那么它就会无止境地等待下去。
WaitForSingleObject
函数
DWORD WaitForSingleObject( HANDLE hHANDLE, DWORD dwMilliseconds ); |
使用 WaitForMultipleObjects
函数来等待多个被通知的对象。在信号线程同步对象中,当计数器变为零时,对象是 non-signaled。
WaitForMultipleObjects
函数
DWORD WaitForMultipleObjects( DWORD nCount, Const HANDLE* lpHandles, BOOL bWaitAll, DWORD dwMilliseconds ); |
Linux POSIX 信号使用 sem_wait()
来挂起发出调用的线程,直到信号拥有了非零的计数值。然后它自动地减少信号的计数值。
sem_wait()
函数
int sem_wait(sem_t * sem); |
在 POSIX 信号中不能使用超时选项。不过,您可以通过在某个循环中执行非阻塞的 sem_trywait()
来完成此功能,它会计算超时的值。
sem_trywait()
函数
int sem_trywait(sem_t * sem); |
在 Linux 中,pthread_cond_wait()
会阻塞发出调用的线程。发出调用的线程会减小那个信号。如果当 pthread_cond_wait
被调用时信号是零,则 pthread_cond_wait()
就会阻塞,直到另一个线程增加了那个信号的值。
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
,当调用者检查条件表达式时必须持有它。
Win32 代码 | 相应的 Linux 代码 |
DWORD retVal; |
if (rc = pthread_mutex_lock(&(token->mutex))) |
如果您需要在指定的一段时间内阻塞发出调用的线程,那么请使用 pthread_cond_timewait
来阻塞它。调用这个方法来等待所需要信号上某个事件的完成,等待指定的一段时间。
int pthread_cond_timewait( pthread_cond_t *cond, pthread_mutex_t *mutex, timespec *tm ); |
Win32 代码 | 相应的 Linux 代码 |
retVal = WaitForSingleObject(SemHandle, timelimit); |
int rc; |
回页首
POSIX 信号示例代码
清单 22 使用 POSIX 信号来实现线程 A 和 B 之间的同步:
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); |
回页首
进程内信号示例代码
#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); } |
#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 ); |
回页首
进程间信号示例代码
#include <stdio.h> #include <windows.h> #define WAIT_FOR_ENTER printf( "Press ENTER\n" );getchar() int main() { HANDLE semaphore; int nRet; DWORD retVal; SECURITY_ATTRIBUTES sec_attr; printf( "Inter Process Semaphore test - Process 1.\n" ); printf( "Start.\n" ); sec_attr.nLength = sizeof( SECURITY_ATTRIBUTES ); sec_attr.lpSecurityDescriptor = NULL; sec_attr.bInheritHandle = TRUE; semaphore = CreateSemaphore( &sec_attr, 1, 65536, “456789" ); if( semaphore == (HANDLE) NULL ) return RC_OBJECT_NOT_CREATED; printf( "Semaphore created. (%lx)\n", nRet ); WAIT_FOR_ENTER; if( ! ReleaseSemaphore(semaphore, 1, NULL) ) return SEM_POST_ERROR; printf( "Semaphore Posted. \n"); WAIT_FOR_ENTER; retVal = WaitForSingleObject (semaphore, INFINITE ); if (retVal == WAIT_FAILED) return SEM_WAIT_ERROR; printf( "Wait for Semaphore. \n"); WAIT_FOR_ENTER; CloseHandle (semaphore); printf( "Semaphore deleted.\n" ); printf( "Stop.\n" ); return 0; } |
清单 26 给出了作为支持进程间共享的有名称信号示例的消息 IPC 代码。
#include <stdio.h> #include <sys/types.h> #include <sys/sem.h> #include <sys/stat.h> #include <errno.h> #include <unistd.h> #define WAIT_FOR_ENTER printf( "Press ENTER\n" );getchar() struct msgbuf { long mtype; /* type of message */ char mtext[1]; /* message text */ }; int main() { key_t msgKey; int flag; struct msgbuf buff; int sem; int nRet =0; printf( "Inter Process Semaphore test - Process 1.\n" ); printf( "Start.\n" ); flag = IPC_CREAT|IPC_EXCL; if( ( msgKey = (key_t) atol( "456789" ) ) <= 0 ) return 1; flag |= S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; sem = (int) msgget( msgKey, flag ); if (sem == -1) if( errno == EEXIST ) { flag &= ~IPC_EXCL; sem = (int) msgget( msgKey, flag ); if (msgctl(sem, IPC_RMID, NULL ) != 0) return 1; sem = (int) msgget( msgKey, flag ); if (sem == -1) return 1; } else return 1; printf( "Semaphore created. \n" ); WAIT_FOR_ENTER; buff.mtype = 123; if( msgsnd( sem, &buff, 1, 0 ) < 0 ) return 1; printf( "Semaphore Posted. \n" ); WAIT_FOR_ENTER; if( msgrcv( sem, &buff, 1, 0, 0 ) < 0 ) return 1; printf( "Wait for Semaphore. \n" ); WAIT_FOR_ENTER; msgctl(sem, 0, IPC_RMID ); printf( "Semaphore deleted.\n" ); printf( "Stop.\n" ); return 0; } |
#include <stdio.h> #include <windows.h> int main() { HANDLE semaphore; DWORD retVal; printf( "Inter Process Semaphore test - Process 2.\n" ); printf( "Start.\n" ); SECURITY_ATTRIBUTES sec_attr; sec_attr.nLength = sizeof( SECURITY_ATTRIBUTES ); sec_attr.lpSecurityDescriptor = NULL; sec_attr.bInheritHandle = TRUE; semaphore = CreateSemaphore( &sec_attr, 0, 65536, “456789" ); if( semaphore == (HANDLE) NULL ) return RC_OBJECT_NOT_CREATED; printf( "Semaphore opened. (%lx)\n", nRet ); printf( "Try to wait for semaphore.\n" ); while( ( retVal = WaitForSingleObject( semaphore, 250 ) ) == WAIT_TIMEOUT) printf( "Timeout. \n"); printf( "Semaphore acquired. \n"); printf( "Try to post the semaphore.\n" ); if( ! ReleaseSemaphore(semaphore, 1, NULL) ) return RC_SEM_POST_ERROR; printf( "Semaphore posted. \n"); CloseHandle(semaphore); printf( "Semaphore closed. \n"); printf( "Stop.\n" ); return 0; } |
#include <stdio.h> #include <sys/time.h> #include <sys/types.h> #include <sys/sem.h> #include <sys/stat.h> #include <errno.h> #include <unistd.h> #define RC_TIMEOUT = 3 struct msgbuf { long mtype; /* type of message */ char mtext[1]; /* message text */ }; int main() { key_t msgKey; int flag=0; struct msgbuf buff; int sem; int nRet =0; printf( "Inter Process Semaphore test - Process 2.\n" ); printf( "Start.\n" ); if( ( msgKey = (key_t) atol( "456789" ) ) <= 0 ) return 1; flag |= S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; sem = (int) msgget( msgKey, flag ); if (sem == -1) if( errno == EEXIST ) { flag &= ~IPC_EXCL; sem = (int) msgget( msgKey, flag ); if (msgctl(sem, IPC_RMID, NULL ) != 0) return 1; sem = (int) msgget( msgKey, flag ); if (sem == -1) return 1; } else return 1; printf( "Semaphore opened. (%lx)\n", nRet ); if( nRet != 0 ) return 0; printf( "Try to wait for semaphore.\n" ); while( ( nRet = sem_shared_wait_timed( sem, 250 ) ) == 3) printf( "Timeout. (%lx)\n", nRet ); printf( "Semaphore acquired. (%lx)\n", nRet ); printf( "Try to post the semaphore.\n" ); buff.mtype = 123; if( msgsnd( sem, &buff, 1, 0 ) < 0 ) return 1; printf( "Semaphore posted. (%lx)\n", nRet ); if( nRet != 0 ) return 0; printf( "Semaphore closed. (%lx)\n", nRet ); printf( "Stop.\n" ); return 0; } int sem_shared_wait_timed( int sem, unsigned long timelimit) { struct msgbuf buff; struct timeval timeOut; int msg[1]; int nRet=0; timeOut.tv_sec = timelimit / 1000; timeOut.tv_usec = (timelimit % 1000) * 1000; msg[0] = sem; nRet = select( 0x1000, (fd_set *)msg, NULL, NULL, &timeOut ); if(nRet == 0) return 3; if( msgrcv( sem, &buff, 1, 0, 0 ) < 0 ) return 1; } |
回页首
结束语
本系列的第三篇文章从信号 API 的角度讲述了从 Win32 到 Linux 的映射,并给出了您可以引用的信号示例代码。线程化的、同步的系统所提出的挑战不仅是设计与实现,也包括了质量保证的所有阶段。当进行从 Win32 到 Linux 的迁移时,可以将这些文章做为参考。一定要去阅读本系列中以前的文章。