我计划使用以下函数作为替代,但后来我发现 sem_getvalue() 在 OSX 上也已被弃用且无法使用。您可以在 MIT 或 LGPL 许可(您的选择)下自由使用以下未经测试的代码。
#ifdef __APPLE__
struct CSGX__sem_timedwait_Info
{
pthread_mutex_t MxMutex;
pthread_cond_t MxCondition;
pthread_t MxParent;
struct timespec MxTimeout;
bool MxSignaled;
};
void *CSGX__sem_timedwait_Child(void *MainPtr)
{
CSGX__sem_timedwait_Info *TempInfo = (CSGX__sem_timedwait_Info *)MainPtr;
pthread_mutex_lock(&TempInfo->MxMutex);
// Wait until the timeout or the condition is signaled, whichever comes first.
int Result;
do
{
Result = pthread_cond_timedwait(&TempInfo->MxCondition, &TempInfo->MxMutex, &TempInfo->MxTimeout);
if (!Result) break;
} while (1);
if (errno == ETIMEDOUT && !TempInfo->MxSignaled)
{
TempInfo->MxSignaled = true;
pthread_kill(TempInfo->MxParent, SIGALRM);
}
pthread_mutex_unlock(&TempInfo->MxMutex);
return NULL;
}
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout)
{
// Quick test to see if a lock can be immediately obtained.
int Result;
do
{
Result = sem_trywait(sem);
if (!Result) return 0;
} while (Result < 0 && errno == EINTR);
// Since it couldn't be obtained immediately, it is time to shuttle the request off to a thread.
// Depending on the timeout, this could take longer than the timeout.
CSGX__sem_timedwait_Info TempInfo;
pthread_mutex_init(&TempInfo.MxMutex, NULL);
pthread_cond_init(&TempInfo.MxCondition, NULL);
TempInfo.MxParent = pthread_self();
TempInfo.MxTimeout.tv_sec = abs_timeout->tv_sec;
TempInfo.MxTimeout.tv_nsec = abs_timeout->tv_nsec;
TempInfo.MxSignaled = false;
sighandler_t OldSigHandler = signal(SIGALRM, SIG_DFL);
pthread_t ChildThread;
pthread_create(&ChildThread, NULL, CSGX__sem_timedwait_Child, &TempInfo);
// Wait for the semaphore, the timeout to expire, or an unexpected error condition.
do
{
Result = sem_wait(sem);
if (Result == 0 || TempInfo.MxSignaled || (Result < 0 && errno != EINTR)) break;
} while (1);
// Terminate the thread (if it is still running).
TempInfo.MxSignaled = true;
int LastError = errno;
pthread_mutex_lock(&TempInfo.MxMutex);
pthread_cond_signal(&TempInfo.MxCondition);
pthread_mutex_unlock(&TempInfo.MxMutex);
pthread_join(ChildThread, NULL);
pthread_cond_destroy(&TempInfo.MxCondition);
pthread_mutex_destroy(&TempInfo.MxMutex);
// Restore previous signal handler.
signal(SIGALRM, OldSigHandler);
errno = LastError;
return Result;
}
#endif
SIGALRM 比 SIGUSR2 更有意义,因为这里显然使用了另一个示例(我没有费心看它)。SIGALRM 主要用于 alarm() 调用,当您需要亚秒级分辨率时,这实际上是无用的。
此代码首先尝试使用 sem_trywait() 获取信号量。如果这立即成功,那么它就会退出。否则,它会启动一个线程,该线程是通过 pthread_cond_timedwait() 实现定时器的地方。MxSignaled 布尔值用于确定超时状态。
您可能还会发现此相关函数对于调用上述 sem_timedwait() 实现很有用(同样,MIT 或 LGPL,您的选择):
int CSGX__ClockGetTimeRealtime(struct timespec *ts)
{
#ifdef __APPLE__
clock_serv_t cclock;
mach_timespec_t mts;
if (host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock) != KERN_SUCCESS) return -1;
if (clock_get_time(cclock, &mts) != KERN_SUCCESS) return -1;
if (mach_port_deallocate(mach_task_self(), cclock) != KERN_SUCCESS) return -1;
ts->tv_sec = mts.tv_sec;
ts->tv_nsec = mts.tv_nsec;
return 0;
#else
return clock_gettime(CLOCK_REALTIME, ts);
#endif
}
帮助使用与 clock_gettime() 可以提供的最接近的东西填充 timespec 结构。有各种各样的评论说重复调用 host_get_clock_service() 是昂贵的。但是启动线程也很昂贵。
真正的解决方案是让 Apple 实现整个 POSIX 规范,而不仅仅是强制性部分。只实施 POSIX 的强制性部分,然后声称 POSIX 合规性只会让每个人都有一个半损坏的操作系统和大量类似上述的变通方法,这些变通方法的性能可能不太理想。
综上所述,我放弃了 Mac OSX 和 Linux 上的本地信号量(Sys V 和 POSIX)。它们以很多相当不幸的方式被破坏。其他人也应该放弃他们。(我不会放弃那些操作系统上的信号量,只是放弃本机实现。)无论如何,现在每个人都有一个没有商业限制的 sem_timedwait() 实现,其他人可以复制意大利面到他们心中的内容。