信号和事件--事件篇
2012-07-17 09:49
120 查看
事件
在 Windows 中,事件对象是那些需要使用
在 手工重置事件(manual reset event) 中,对象的状态会一直维持为有信号状态,直到使用
在 自动重置事件(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 pthreads 使用
可以使用
等待某个事件
在 Windows 中,等待函数提供了获取同步对象的机制。我们可以使用不同类型的等待函数(此处我们只考虑
在这段代码中:
Linux POSIX 信号量使用
在 POSIX 信号量中并没有提供超时操作。这可以通过在一个循环中执行非阻塞的
Linux pthreads 使用
改变事件对象的状态
函数
Linux POSIX 信号量使用
调用
注意,条件函数并不是异步信号安全的,因此不能在信号处理函数中调用。具体地说,在信号处理函数中调用
重置事件的状态
在 Windows 中,
在 Linux 中,条件变量和 POSIX 信号量都是自动重置类型的。
关闭/销毁事件对象
在 Windows 中,
在这段代码中,
在 Linux 中,
有名事件对象
在 Linux 中,进程之间有名事件对象所实现的功能可以使用 System V 信号量实现。System V 信号量是计数器变量,因此可以实现 Windows 中事件对象的功能,信号量的计数器的初始值可以使用
要将某个事件的状态修改为有信号状态,可以使用
-1,这样就可以阻塞调用进程,直到它变为有信号状态为止。
可以通过使用
例子
下面几个例子可以帮助您理解我们在这一节中所讨论的内容。
清单 4. Windows 无名事件对象的代码
清单 5. Linux 使用 POSIX 信号量的等效代码
清单 6. Linux 中使用条件变量的等效代码
清单 7. Windows 中使用有名事件的例子
清单 8. Linux 中使用 System V 信号量的等效代码
本系列下一篇文章的内容
本文是这一系列的第 2 部分,这篇文章从信号量和事件入手,介绍了有关同步对象和原语的内容。第 3 部分的内容将涉及互斥、临界区和等待函数。
在 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 ) |
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 ); |
关闭/销毁事件对象
在 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 |
// 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 |
// 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); |
// 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); |
// 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); } |
本文是这一系列的第 2 部分,这篇文章从信号量和事件入手,介绍了有关同步对象和原语的内容。第 3 部分的内容将涉及互斥、临界区和等待函数。
相关文章推荐
- PyQt5(4)——控件事件响应与信号槽
- QT用信号和处理机制为事件添加处理动作(点击按钮显示helloworld)
- QT源码之Qt信号槽机制与事件机制的联系
- 局部QEventLoop帮助QWidget不消失(也就是有一个局部事件循环始终在运行,导致程序被卡住那里,但仍可以接受事件。说白了就是有一个while语句死活不肯退出,直到收到退出信号)
- QT中的事件机制与信号-槽机制
- vxworks中任务间的通信支持信号量、消息队列、管道、信号、事件、共享内存等
- Qt源码分析-事件如何触发信号
- PyQt4学习笔记2:事件和信号
- Qt4.7中,线程,信号,事件的一点理解
- Python Signal(信号) 异步系统事件
- Qt 信号槽机制与事件机制四 提升篇
- Mangos源码分析(12):服务器公共组件实现之事件与信号
- [置顶] (十一)信号事件的管理
- Linux下的信号事件
- GTK+ 中的事件(events)和信号(signals)
- PyQt5教程-09-事件与信号
- pyqt5学习(四)事件和信号
- Mangos源码分析(12):服务器公共组件实现之事件与信号
- QLineEdit单击信号(事件过滤器)
- (12)事件和信号的区别