代わりに次の関数を使用することを計画していましたが、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() 呼び出し用に予約されており、1 秒未満の精度が必要な場合は事実上役に立ちません。
このコードは、最初に 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 準拠を主張すると、半分壊れた OS と上記のような多くの回避策がすべての人に残され、理想的なパフォーマンスとは言えない可能性があります。
以上のことから、私は Mac OSX と Linux の両方でネイティブ セマフォ (Sys V と POSIX の両方) をあきらめています。それらはかなりの数のかなり残念な方法で壊れています。他の人もあきらめるべきです。(私はこれらの OS のセマフォをあきらめるのではなく、ネイティブ実装だけです。) いずれにせよ、今では誰もが商用制限なしで sem_timedwait() 実装を持っており、他の人は心ゆくまでコピーパスタできます。