怎样治老年精神障碍:将应用程序从 OS/2 移植到 Linux 上:第 1 部分,线程、互斥锁、信号量
来源:百度文库 编辑:偶看新闻 时间:2024/05/08 12:25:06
本文介绍了有关线程和同步原语映射方面的知识。本系列的后续文章将会介绍系统调用、网络调用和其他编程指令的情况。
线程
线程是在 OS/2 上的基本执行单元,也是 OS/2 进程内部的可调度单元。线程的调度及优先级是完全在内核中处理的。
Linux 内核使用的是进程模型,而不是线程模型。然而,Linux 内核为创建线程提供了一种轻量级的进程框架,而线程的真正实现是在用户空间中。目前,可用的线程库有很多种(LinuxThreads、NGPT、NPTL,等等)。本文的研究是以 LinuxThreads 库为基础进行的,但是其内容对 Rad Hat 的 Native POSIX Threading Library(本地 POSIX 线程库,NPTL)也同样适用。
本节介绍 OS/2 和 Linux 中的线程机制。内容涉及创建线程、设置线程属性以及改变线程优先级等所使用的调用。OS/2 与 Linux 中这些调用的对应关系如下表所示:
表 1. 线程映射表
OS/2 Linux 类别 _beginthread pthread_create
pthread_attr_init
pthread_attr_setstacksize
pthread_attr_destroy 可映射的 _endthread pthread_exit 可映射的 DosWaitThread pthread_join
pthread_attr_setdetachstate
pthread_detach 可映射的 DosSetPriority 进程
setpriority
sched_setscheduler
sched_setparam
线程
pthread_setschedparam
pthread_setschedpolicy
pthread_attr_setschedparam
pthread_attr_setschedpolicy 特定于上下文的
类别一列表示该 OS/2 指令是可映射的,还是特定于上下文的:
可映射的:这条 OS/2 指令可以映射为一条(或者若干条)指定的 Linux 指令,这需要对类型、参数、返回代码等进行检查。
特定于上下文的:给定的 OS/2 指令在 Linux 中可能有等价的指令,也可能没有,或者是 Linux 中能够提供相似功能的指令不止一条。不论是哪种情况,我们都要根据应用程序的上下文来决定具体使用哪一条(或哪几条)Linux 指令。
线程的创建
在 OS/2 中,我们用 _beginthread 这个系统调用来产生线程:
int _beginthread(void (*start_address) (void *), (void *) stack,
unsigned stack_size, void *arg);
Linux 则调用 pthread 库中的 pthread_create() 来产生线程:
int pthread_create (pthread_t *thread_id, pthread_attr_t *threadAttr,
void * (*start_address)(void *), void * arg);
指定线程函数
在 OS/2 系统调用 _beginthread 中,参数 start_address 表示新创建的线程将要执行的函数的地址。这个线程函数必须用 _Optlink 链接标记进行声明和编译。
在 Linux 库调用 pthread_create() 中,参数 start_address 表示新创建的线程将要执行的函数的地址。
向线程函数传递参数
在 OS/2 中,系统调用 _beginthread() 的参数 arg 用于指定要传递给新建线程的参数。它指定了要传递给新建线程的数据项的地址。
在 Linux 中,库调用 pthread_create() 的参数 arg 用于指定要传递给新建线程的参数。
设置堆栈大小
OS/2 系统调用 call _beginthread() 的参数 stack_size 表示将要分配给新建线程的堆栈大小,单位为字节。堆栈大小是 4K 的正整数倍,最小为 8K。
在 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_setxxxx 只能用于在创建线程之前对将要作为参数传递给 pthread_create 的属性对象进行更新。同理,在创建好线程之后,我们可以使用任何形如 pthread_xxxx 的调用。
pthread_attr_setstacksize() 可以用于设置堆栈大小:
int pthread_attr_setstacksize(pthread_attr_t *threadAttr, int
stack_size);
线程状态
在 OS/2 中并不存在显式地维护与线程终止有关的线程状态。不过,我们可以通过调用 DosWaitThread() ,让一个线程显式地等待进程内某个特定或非特定线程的终止。
在 Linux 中,缺省情况下线程是以一种可连接(joinable)状态被创建的。在可连接状态下,其他线程可以在一个线程终止时对其进行同步,然后用 pthread_join() 函数恢复其终止代码。这个可连接线程的线程资源只有在它被插入之后才能释放。
OS/2 用 DosWaitThread() 来等待一个线程的终止:
APIRET DosWaitThread(PTID ptid, ULONG option);
Linux 用 pthread_join 完成相同的工作 :
int pthread_join(pthread_t *thread, void **thread_return);
在分离(detached)状态下,线程的资源在线程终止时会被立即释放。您可以通过对线程属性对象调用 pthread_attr_setdetachstate() 来设置分离状态:
int pthread_attr_setdetachstate (pthread_attr_t *attr, int
detachstate);
以可连接状态创建的线程以后还可以使用 pthread_detach() 调用来进入分离状态:
int pthread_detach (pthread_t id);
线程的退出
在 OS/2 中,系统调用 _endthread() 用于终止线程。
Linux 中的等价库调用为 pthread_exit() 。 retval 表示线程的返回值,其他线程可以通过调用 pthread_join() 获取这个值:
int pthread_exit(void* retval);
改变优先级
OS/2 用系统调用 DosSetPriority() 改变进程,或正在运行的进程中的线程的优先级:
int DosSetPriority(int scope, int class, int delta, int id);
在本例中,scope 是一个进程的 PRTYS_PROCESS ;一个线程级别的 PRTYS_THREAD 是该线程的优先级。下面列出可以设置的不同优先级:
不改变: PRTYC_NOCHANGE
空闲时间: PRTYC_IDLETIME
常规: PRTYC_REGULAR
时间关键型: PRTYC_TIMECRITICAL
固定高的优先级: PRTYC_FOREGROUNDSERVER
其中,参数 delta 是应用于该进程优先级的改变量。其值必须在 -31 到 +31 之间。该值越高,优先级就越高。当前进程或线程的 id 都为 0。
Linux 中提供了很多调用,可以用来修改或改变线程的优先级。您应该根据应用程序的上下文环境选择使用不同的调用。
普通或常规的进程/线程
Linux 系统调用 setpriority() 用来设置或修改普通进程和线程的优先级。参数 scope 是 PRIO_PROCESS 。将 id 设为 0,可以修改当前进程(或线程)的优先级。与前面一样, delta 也是一个优先级数值 —— 不过这一次 delta 的范围变成了 -20 到 20。还要注意:在 Linux 中,delta 值越小,优先级就越高。因此您可以对 IDLETIME 优先级将 delta 设置为 +20,而对 REGULAR 优先级则将 delta 设置为 0。
在 OS/2 中,优先级的范围是从 0 (优先级最低)到 31 (优先级最高)。但是在 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 (实时的轮询策略)和 SCHED_FIFO (实时的 FIFO 策略)。
清单 1. 修改调度优先级
struct sched_param {
...
int sched_priority;
...
};
此处, param 是指向一个代表调度优先级的结构的指针。对于实时调度策略来说,该值的范围为从 1 到 99。对于其他策略(普通的非实时进程)来说,该值是 0。
在 Linux 中,对于一种已知的调度策略来说,我们也可以使用系统调用 sched_setparam 来只修改进程的优先级:
int sched_setparam(pit_t pid, const struct sched_param *param);
LinuxThreads 库中的 pthread_setschedparam 调用是 sched_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, conststruct sched_param *param);
下面这个例子可以非常清楚地说明创建线程和修改优先级的实现从 OS/2 到 Linux 的映射方式。
线程的例子
在这个 OS/2 的例子中,thread1 是一个普通的线程,而 thread2 则是一个时间关键型实时线程。
清单 2. OS/2 线程代码
main () {
enum {StackSize = 120*1024}; /* stack size set to 120 K */
/* create a thread */
int thread1 = _beginThread (RegularThread, NULL, StackSize,
NULL);
int thread2 = _beginThread (CriticalThread, NULL,
StackSize, NULL);
}
/* Normal /Regular Priority Thread */
static void RegularThread (void *) {
/* Set the priority of the thread. 0 is passed as an
argument to identify the current thread */
int iRc = DosSetPriority(PRTYS_THREAD, PRTYC_REGULAR, 20,
0);
_endThread();
}
/* Realtime time critical Priority Thread */
static void CriticalThread (void *) {
int iRc = DosSetPriority(PRTYS_THREAD, PRTYC_TIMECRITICAL,
20, 0);
_endThread();
}
下面是与上面这段 OS/2 代码等价的 Linux 代码:
清单 3. 等价的 Linux 线程代码
#define STATIC 0
static void * RegularThread (void *);
static void * CriticalThread (void *);
/* Regular non-realtime Thread function */
static void * RegularThread (void *d)
{
int priority = 10; //0 for Regular, +20 for Idletime
/* Set the priority - normal non-realtime priority */
int irc = setpriority(PRIO_PROCESS, 0, priority);
pthread_exit(NULL);
}
/* Time Critical Realtime Thread function */
static void * CriticalThread (void *d)
{
if (STATIC == 0) {
/* change the thread priority dynamically */
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 = 99;
int irc = pthread_setschedparam(thread_id, policy, ¶m);
}
pthread_exit(NULL);
}
int main (void)
{
pthread_t thread1, thread2; // thread identifiers
pthread_attr_t threadAttr1, threadAttr2; // thread attributes
struct sched_param param; // scheduling priority
int policy = SCHED_RR; // scheduling policy - real time
int irc, rc;
rc = pthread_attr_init(&threadAttr1); /* init the attr 1*/
rc = pthread_attr_init(&threadAttr2); /* init the attr 2*/
/* Set the stack size of the thread */
irc = pthread_attr_setstacksize(&threadAttr1, 120*1024);
irc = pthread_attr_setstacksize(&threadAttr2, 120*1024);
/* Set thread to detached state. No need for pthread_join*/
irc = pthread_attr_setdetachstate(&threadAttr1,
PTHREAD_CREATE_DETACHED);
irc = pthread_attr_setdetachstate(&threadAttr2,
PTHREAD_CREATE_DETACHED);
if (STATIC == 1) {
/* priority is set statically */
/* Set the policy of the thread to real time*/
irc = pthread_attr_setschedpolicy(&threadAttr2, policy);
/* Set the scheduling priority of the thread - max
priority*/
param.sched_priority = 99;
irc = pthread_attr_setschedparam(&threadAttr2, ¶m);
}
/* Create the threads */
irc = pthread_create(&thread1, &threadAttr1, RegularThread, NULL);
irc = pthread_create(&thread2, &threadAttr2, CriticalThread,
NULL);
/* Destroy the thread attributes */
irc = pthread_attr_destroy(&threadAttr1);
irc = pthread_attr_destroy(&threadAttr2);
}
互斥锁
在互斥锁的映射中,要考虑以下几点:
互斥锁的类型:OS/2 中互斥锁信号量缺省是递归的,而在 Linux 中则并非如此。
初始状态:在 OS/2 中,互斥锁在创建时就可以获得,而在 Linux 中则并不支持这种方式。要在 Linux 中达到同样的功能,在创建之后必须对互斥锁显式地加锁。
信号量:OS/2 支持有名信号量和无名信号量,而在 Linux pthreads 的当前实现中,并不支持这种功能。
超时:在 OS/2 中,在获取互斥锁时可以指定超时时间;而在 Linux 中并不支持这种功能(本文后文中会加以介绍)。
互斥锁的映射如下表所示:
表 2. 互斥锁映射表
OS/2 Linux 类别 DosCreateMutexSem
DosRequestMutexSem
DosReleaseMutexSem
DosCloseMutexSem pthread_mutex_init
pthread_mutex_lock/pthread_mutex_trylock
pthread_mutex_unlock
pthread_mutex_destroy 可映射的
创建互斥锁
在 OS/2 中,系统调用 DosCreateMutexSem() 用来创建互斥锁:
APIRET DosCreateMutexSem (PSZ pszName, PHMTX phmtx, ULONG flAttr, BOOL32 fState);
参数 pszName 是用 ASCII 字母表示的互斥锁名字。
在 Linux 中,我们可以使用 pthread 库的 pthread_mutex_init() 调用来创建互斥锁:
int pthread_mutex_init(pthread_mutex_t *mutex, const
pthread_mutexattr_t *mutexattr);
在 Linux 中,有“三种”互斥锁。互斥锁的“类型”决定了如果一个线程试图对一个已经使用 pthread_mutex_lock 加锁过了的互斥锁再加锁会产生什么问题:
快速互斥锁:在试图使用 pthread_mutex_lock() 对互斥锁加锁时,调用线程永远挂起。
递归互斥锁: pthread_mutex_lock() 立即返回,返回值是一个成功代码。
带错误检验的互斥锁: pthread_mutex_lock() 立即返回,返回值是错误代码 EDEADLK。
我们可以使用两种方法来设置互斥锁的“类型”。静态的设置方法如下:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
/* For Fast mutexes */pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
/* For recursive mutexes */
pthread_mutex_t errchkmutex =
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;/* For errorcheck mutexes */
另外一种设置互斥锁“类型”的方法是使用一个互斥锁的属性对象。要实现这种功能,就要在调用 pthread_mutexattr_init() 来初始化对象之后再调用 pthread_mutexattr_settype() 来设置互斥锁的“类型”:
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);
参数“kind”允许的值如下所示:
PTHREAD_MUTEX_FAST_NP
PTHREAD_MUTEX_RECURSIVE_NP
PTHREAD_MUTEX_ERRORCHECK_NP
属性对象可以使用 pthread_mutexattr_destroy() 销毁:
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
加锁或获取互斥锁
OS/2 使用 DosRequestMutexSem() 对互斥锁加锁(或称为获取互斥锁):
APIRET DosRequestMutexSem(HMTX hmtx, ULONG ulTimeout);
参数 ulTimeOut 指明该线程要被锁定的最长时间:
在 Linux 中,pthread 库调用 pthread_mutex_lock() 和 pthread_mutex_trylock() 都可以用来获取互斥锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
在这两个调用中,第一个 pthread_mutex_lock() 是一个阻塞调用 —— 这就是说,如果一个互斥锁已经被其他线程加锁了,那么 pthread_mutex_lock() 就会挂起产生调用的线程,直到该互斥锁被释放为止。
而 pthread_mutex_trylock() 则不同,如果这个互斥锁已经被其他进程加锁,那么 pthread_mutex_trylock() 就会立即返回。
注意,在 Linux 中并没有超时选项。在一个循环(该循环计数超时值)的延时期间执行一个非阻塞的 pthread_mutex_trylock() 调用,也能达到同样的效果。(示例代码请参考 清单 6)。
解锁或释放互斥锁
OS/2 使用 DosReleaseMutexSem() 来释放对一个互斥信号量的所有权:
APIRET DosReleaseMutexSem(HMTX hmtx);
Linux 使用 pthread_mutex_unlock() 来释放互斥锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
注意,互斥函数并不是异步信号安全的,也不应该在信号处理函数中调用。通常,在一个信号处理函数中调用 pthread_mutex_lock 或 pthread_mutex_unlock 可能会引起调用线程的死锁。
销毁互斥锁
在 OS/2 中, DosCloseMutexSem() 用来关闭一个互斥信号量:
APIRET DosCloseMutexSem (HMTX hmtxSem);
在 Linux 中, pthread_mutex_destroy() 用来销毁一个互斥对象,并释放它持有的所有资源。这个调用还会检查当时这个互斥锁是否已经被释放了:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
设置状态
在 OS/2 中, DosCreateMutexSem() 调用的 fState 参数用来设置互斥锁的初始状态。 fState 允许为以下的两个值:
值 1 表示创建一个初始状态为已经拥有的互斥锁。
值 0 表示创建一个初始状态为未拥有的互斥锁。
在 Linux 中,我们并不能使用 pthread_mutex_init() 调用来设置互斥锁的状态,但是可以使用以下步骤来达到这个目的:
使用 pthread_mutex_init() 创建一个互斥锁。
使用 pthread_mutex_lock() 对这个互斥锁进行加锁(或称为获取这个互斥锁)。
互斥锁的例子
清单 4. OS/2 互斥锁代码
hmtx hmtxSem; // semaphore handle
unsigned long ulRc; // return code
/* Create a un named mutex semaphore */
ulRc = DosCreateMutexSem (NULL, &hmtxSem, 0, FALSE);
ulRc = DosRequestMutexSem (hmtxSem, (unsigned long)
SEM_INDEFINITE_WAIT);
/* Access the shared resource */
...
/* Release the mutex */
ulRc = DosReleaseMutexSem(hmtxSem);
...
/* Closes the semaphore */
ulrc = DosCloseMutexSem (hmtxSem);
请试着将这段代码与下面的 Linux 代码比较一下:
清单 5. Linux 互斥锁代码
/* Declare the mutex */
pthread_mutex_t mutex;
/* Attribute for the mutex */
pthread_mutexattr_t mutexattr;
/* Set the mutex as a recursive mutex */
pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE_NP);
/* create the mutex with the attributes set */
pthread_mutex_init(&mutex, &mutexattr);
/* lock the mutex */
pthread_mutex_lock (&mutex);
/* access the shared resource */
..
/* unlock the mutex */
pthread_mutex_unlock (&mutex);
...
/* destroy the attribute */
pthread_mutexattr_destroy(&mutexattr)
/* Close/destroy the semaphore */
irc = pthread_mutex_destroy (&mutex);
下面这个例子说明了在试图获取一个互斥锁时,如何模拟超时功能:
清单 6. 在 Linux 中模拟超时功能
#define TIMEOUT 100 /* 1 sec */
struct timespec delay;
/* Declare the mutex */
pthread_mutex_t mutex;
while (timeout < TIMEOUT ) {
delay.tv_sec = 0;
delay.tv_nsec = 1000000; /* 1 milli sec */
irc = pthread_mutex_trylock(&mutex);
if (!irc) {
/* we now own the mutex */
break;
}
else {
/* check whether somebody else has the mutex */
if (irc == EPERM ) {
/* sleep for delay time */
nanosleep(&delay, NULL);
timeout++ ;
}
else{
/* error */
}
}
}
信号量
除了 pthread 条件变量之外,Linux 还提供了 POSIX 信号量来映射 OS/2 的事件信号量构造(Linux 还提供了 SVR 兼容的 semop、semctl 等类似的调用,但是本文的目的仅限于介绍 POSIX 和 LinuxThreads 实现)。它们都有自己的优点和缺点,您需要根据应用程序的逻辑来决定到底使用哪种技术。在对事件信号量进行映射时,要考虑以下几点:
信号量的类型:OS/2 支持有名和无名的事件信号量,有名信号量可以在进程间进行共享。Linux 不支持这种功能。
初始状态:在 OS/2 中,信号量可以有一个初值。在 Linux 中, 虽然 POSIX 信号量支持这种功能,但是 pthreads 并不支持这种功能。在使用 pthreads 时要考虑这个问题。
超时:OS/2 事件信号量支持超时等待。在 Linux 中,POSIX 信号量实现只支持不确定的等待(阻塞)。pthreads 实现既可以支持阻塞,也可以支持超时。 pthread_cond_timedwait() 调用提供了一个等待时的超时时间,而 pthread_cond_wait() 则用于不确定的等待。
信号:在 OS/2 中,唤醒一个信号量会唤醒正在等待这个信号量的所有线程。在 Linux 中,POSIX 线程实现一次只会唤醒一个线程,而 pthreads 实现有一个 pthread_cond_signal() 调用,它可以唤醒一个线程;还有一个 pthread_cond_broadcast() 调用,可以唤醒所有正在等待这个信号量的线程。
同步:在 OS/2 中,事件信号量是异步的。在 Linux 中,POSIX 信号量是异步的,而 pthreads 条件变量是同步的。
总之,只有在不需要超时和广播唤醒的情况下才可以考虑 POSIX 信号量。如果应用程序逻辑需要广播唤醒和超时,可以使用 pthread 条件变量。
表 3. 信号量映射表
OS/2 POSIX Linux 调用 pthread Linux 调用 类别 DosCreateEventSem sem_init pthread_cond_init 上下文相关 DosPostEventSem sem_post pthread_cond_signal / pthread_cond_broadcast 上下文相关 DosWaitEventSem sem_wait/sem_trywait pthread_cond_wait / pthread_cond_timedwait 上下文相关 DosCloseEventSem sem_destroy pthread_cond_destroy 上下文相关
创建事件信号量
OS/2 使用 DosCreateEventSem() 调用来创建事件信号量:
APIRET DosCreateEventSem (PSZ pszName,PHEV phev, ULONG flAttr, BOOL32 fState);
此处, pszName 是一个指向信号量的 ASCII 名的指针。如果该参数为 NULL,那么 DosCreateEventSem() 就会创建一个无名的事件信号量。fState 用来设置事件信号量的状态。如果该值为 0,那么该信号量最初就被重置;如果该值为 1,那么该信号量就被发出。
在 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_t 类型的条件变量可以使用常量 PTHREAD_COND_INITIALIZER 静态地进行初始化;还可以使用 pthread_condattr_init() 进行初始化,后者会对与这个条件变量有关的属性进行初始化。系统调用 pthread_condattr_destroy() 用来销毁属性:
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
等待事件信号量
在 OS/2, DosWaitEventSem() 被用来阻塞一个线程并等待事件信号量:
APIRET DosWaitEventSem (HEV hev, ULONG ulTimeOut);
此处参数 ulTimeOut 指明了超时时间。如果在这段指定的时间内,信号量还没有被释放,那么 DosWaitEventSem() 调用就会返回一个错误代码。如果指定的超时时间是 -1 ,那么它就会阻塞调用线程。
Linux POSIX 信号量使用 sem_wait() 挂起调用线程,直到这个信号量的计数为非 0;然后自动减少这个信号量的计数:
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 秒开始经过的时间)。
唤醒事件信号量
OS/2 使用 DosPostEventSem() 来释放事件信号量,这样可以唤醒所有正在等待这个信号量的线程:
APIRET DosPostEventSem (HEV hev);
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 ,可能会导致调用线程的死锁。
销毁事件信号量
OS/2 使用 DosCloseEventSem() 来销毁信号量:
APIRET DosCloseEventSem (HEV hev);
Linux POSIX 信号量使用 sem_destroy() 来销毁信号量:
int sem_destroy(sem_t * sem);
在 Linux pthreads 中, pthread_cond_destroy() 用来销毁条件变量:
int pthread_cond_destroy(pthread_cond_t *cond);
事件信号量的例子
清单 7. OS/2 信号量代码
HEV hevIpcInterrupt;
unsigned long ulPostCnt = 0;
unsigned long ulrc; // return code
unsigned long ulTimeout = 10 ; // timeout value
/* create event semaphore */
DosCreateEventSem (NULL, &hevIpcInterrupt, 0, TRUE);
/* In Thread A */
/* Wait forever for event to be posted */
DosWaitEventSem (hevIpcInterrupt, (unsigned long)
SEM_INDEFINITE_WAIT);
/* immediately unblocked as the semaphore is already posted */
/* Waits until the semaphore is posted */
DosWaitEventSem (hevIpcInterrupt, (unsigned long)
SEM_INDEFINITE_WAIT);
/* In Thread B */
DosPostEventSem(hevIpcInterrupt);
/* Close the semaphore */
ulrc = DosCloseEventSem (hevIpcInterrupt);
下面的 Linux 例子使用 pthread 条件变量,用来在两个线程 A 和 B 之间进行同步:
清单 8. Linux 条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;
struct timeval tvTimeNow; //Absolute current time
struct timezone tzTimeZone; //Timezone
struct timespec tsTimeOut; // Input for timedwait
/* In Thread A */
...
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 */
/* In Thread B */
pthread_mutex_lock(&mutex);
pthread_cond_wait(&condvar, &mutex);
pthread_mutex_unlock(&mutex);
/* Thread B blocks indefinitely */
/* ---------------------------------------------------------------------
--------------------------------------------------------------------- */
/* One way of avoiding losing the signal is as follows */
/* In Thread B - 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 B */
/* Thread A now tries to take the mutex lock
to send the signal but gets blocked */
...
pthread_mutex_lock(&mutex);
/* Thread B: Get the current time */
gettimeofday (&tvTimeNow, &tzTimeZone);
/* Calculate the absolute end time - 10 seconds wait */
tsTimeOut.tv_sec = tvTimeNow.tv_sec + 10 ;
/* Thread B waits for the specified time for the signal to be
posted */
pthread_cond_timedwait (&condvar, &mutex, &tsTimeOut );
/* Thread A now gets the lock and can
signal thread B to wake up */
pthread_cond_signal(&condvar */
pthread_mutex_unlock(&mutex);
... /* Thread B
unblocks upon receipt of signal */
pthread_mutex_unlock (&mutex);
下面的清单使用 POSIX 信号量来实现对线程 A 和 B 之间的同步:
清单 9. Linux 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);
结束语
本文介绍了从 OS/2 到 Linux 的映射中有关线程、互斥锁和事件信号量的知识。在将任何程序从 OS/2 移植到 Linux 上时,您都可以参考这些内容。本文中给出的提示和警告有助于简化您移植程序的设计;有关本文中所提到的所有 Linux 调用的更详细内容,您可以参考相关的手册页。本系列文章的下一篇将介绍有关内存管理、文件处理和设备驱动接口的系统调用的映射。