在Mac OS X上使用POSIX信号量:sem_timedwait的替代方法

15

我正在尝试将一个使用信号量的Linux项目移植到Mac OS X,但是Mac OS X上没有实现一些posix信号量。

在这个移植过程中我遇到了sem_timedwait()函数这个问题。

虽然我不太了解信号量,但从man页中可以看出,sem_wait()sem_timedwait()很接近,并且已经实现了。

从man页中可以看出:

sem_timedwait()函数锁定由sem引用的信号量,就像sem_wait()函数一样。 但是,如果不能锁定信号量而必须等待另一个进程或线程通过执行sem_post()函数来解锁信号量,则此等待将在指定的超时时间到期时终止。

根据我对信号量工作方式的有限了解,我可以看出sem_timedwait()更加安全,但我仍应该能够使用sem_wait()

这是正确的吗?如果不是,我还有哪些其他选择...

谢谢

10个回答

7

超时时间对算法的操作很重要,因此仅使用sem_wait()可能不起作用。

您可以使用sem_trywait(),它在所有情况下都会立即返回。然后,您可以循环并使用您选择的睡眠间隔,每次减少总超时时间,直到您用完超时时间或获取了信号量。

更好的解决方案是重写算法以使用条件变量,然后您可以使用pthread_cond_timedwait()来获得适当的超时时间。


1
一个带有trywait和sleep的循环不会起作用,因为每次进程都会失去在队列中的位置。如果有很多线程或进程试图锁定一个信号量,其中一些线程或进程总是会在它被锁定时遇到它,从而超时。一个忙碌的循环可能会起作用,但这并不是一个解决方案。 - Eugene
请问您如何重写代码,以使用跨进程工作的条件变量呢? - rogerdpack
1
@rogerdpack,我猜你已经有一段时间没有使用macOS中的条件变量来处理进程间事件了。我目前正在处理相同的情况...也许你有什么关于如何实现替代sem_wait的想法吗? - Zohar81
https://github.com/pmahoney/process_shared 可能会有帮助,但我自己不清楚,抱歉! - rogerdpack

6

我曾经在OSX上使用命名信号量,但现在sem_timedwait不可用,而sem_init等则已被弃用。我使用pthread mutex和条件来实现信号量,如下所示,适用于我(OSX 10.13.1)。如果sem_t类型不能容纳指针(即指针是64位的,而sem_t是32位的),则您可能需要创建一个句柄vs结构表并查找。

#ifdef __APPLE__

typedef struct
{
    pthread_mutex_t count_lock;
    pthread_cond_t  count_bump;
    unsigned count;
}
bosal_sem_t;

int sem_init(sem_t *psem, int flags, unsigned count)
{
    bosal_sem_t *pnewsem;
    int result;

    pnewsem = (bosal_sem_t *)malloc(sizeof(bosal_sem_t));
    if (! pnewsem)
    {
        return -1;
    }
    result = pthread_mutex_init(&pnewsem->count_lock, NULL);
    if (result)
    {
        free(pnewsem);
        return result;
    }
    result = pthread_cond_init(&pnewsem->count_bump, NULL);
    if (result)
    {
        pthread_mutex_destroy(&pnewsem->count_lock);
        free(pnewsem);
        return result;
    }
    pnewsem->count = count;
    *psem = (sem_t)pnewsem;
    return 0;
}

int sem_destroy(sem_t *psem)
{
    bosal_sem_t *poldsem;

    if (! psem)
    {
        return EINVAL;
    }
    poldsem = (bosal_sem_t *)*psem;

    pthread_mutex_destroy(&poldsem->count_lock);
    pthread_cond_destroy(&poldsem->count_bump);
    free(poldsem);
    return 0;
}

int sem_post(sem_t *psem)
{
     bosal_sem_t *pxsem;
    int result, xresult;

    if (! psem)
    {
        return EINVAL;
    }
    pxsem = (bosal_sem_t *)*psem;

    result = pthread_mutex_lock(&pxsem->count_lock);
    if (result)
    {
        return result;
    }
    pxsem->count = pxsem->count + 1;

    xresult = pthread_cond_signal(&pxsem->count_bump);

    result = pthread_mutex_unlock(&pxsem->count_lock);
    if (result)
    {
        return result;
    }
    if (xresult)
    {
        errno = xresult;
        return -1;
    }
}

int sem_trywait(sem_t *psem)
{
    bosal_sem_t *pxsem;
    int result, xresult;

    if (! psem)
    {
        return EINVAL;
    }
    pxsem = (bosal_sem_t *)*psem;

    result = pthread_mutex_lock(&pxsem->count_lock);
    if (result)
    {
        return result;
    }
    xresult = 0;

    if (pxsem->count > 0)
    {
        pxsem->count--;
    }
    else
    {
        xresult = EAGAIN;
    }
    result = pthread_mutex_unlock(&pxsem->count_lock);
    if (result)
    {
        return result;
    }
    if (xresult)
    {
        errno = xresult;
        return -1;
    }
    return 0;
}

int sem_wait(sem_t *psem)
{
    bosal_sem_t *pxsem;
    int result, xresult;

    if (! psem)
    {
        return EINVAL;
    }
    pxsem = (bosal_sem_t *)*psem;

    result = pthread_mutex_lock(&pxsem->count_lock);
    if (result)
    {
        return result;
    }
    xresult = 0;

    if (pxsem->count == 0)
    {
        xresult = pthread_cond_wait(&pxsem->count_bump, &pxsem->count_lock);
    }
    if (! xresult)
    {
        if (pxsem->count > 0)
        {
            pxsem->count--;
        }
    }
    result = pthread_mutex_unlock(&pxsem->count_lock);
    if (result)
    {
        return result;
    }
    if (xresult)
    {
        errno = xresult;
        return -1;
    }
    return 0;
}

int sem_timedwait(sem_t *psem, const struct timespec *abstim)
{
    bosal_sem_t *pxsem;
    int result, xresult;

    if (! psem)
    {
        return EINVAL;
    }
    pxsem = (bosal_sem_t *)*psem;

    result = pthread_mutex_lock(&pxsem->count_lock);
    if (result)
    {
        return result;
    }
    xresult = 0;

    if (pxsem->count == 0)
    {
        xresult = pthread_cond_timedwait(&pxsem->count_bump, &pxsem->count_lock, abstim);
    }
    if (! xresult)
    {
        if (pxsem->count > 0)
        {
            pxsem->count--;
        }
    }
    result = pthread_mutex_unlock(&pxsem->count_lock);
    if (result)
    {
        return result;
    }
    if (xresult)
    {
        errno = xresult;
        return -1;
    }
    return 0;
}

#endif

1
Brian - 我想我欠你一杯啤酒,但我能做的最好的就是点个赞。我的 sem_timedwait() 实现使用 SIGUSR2(由 @chad 提到)在多年中一直表现良好,但在最近的一个项目中,它使用信号与程序结构的其余部分发生冲突。我发现我可以通过简单地将 #include <semaphore.h> 替换为 typedef sem_t long int 和你的代码主体来使用你的代码。顺便说一句,在 sem_post() 的末尾你缺少了一个 return 0。你的代码不仅仅是 sem_timedwait() 的简单添加,但对于可以应用的情况似乎非常棒! - KeithS
Brian,如果你正在阅读这篇文章,我有两个问题:1)你确定你需要malloc/free调用而不是直接在输入指针上工作吗?2)我想借用这段代码并将其作为我的Github项目的一部分。我希望对你的代码进行一些更改。你对你的代码有任何许可证偏好/要求吗?当然,感谢你提供的示例! - Stanislav Pankevich
1
1 - 不,你需要使用malloc(或其他方案)为调用sem_init创建内存,该调用传递指向sem_t类型的指针,该类型可能只是足够容纳指针的字节数,这就是它在这里使用的方式,而bosal_sem_t类型(Brian的操作系统抽象层)则包含了一堆东西。你可以发明一个微型分配器(例如bosal_sem_t池[max_sems]),以避免使用malloc/free。2 - 代码可自由使用开源,很高兴它对你有帮助,因为我花了一段时间才弄清楚! - Brian
你好,我使用了你的想法在我的库中实现了信号量。我对你的代码进行了修改:删除了malloc/free,并将条件变量放在while循环内部。实现代码在此链接中:https://github.com/A-Ribeiro/aRibeiroPlatform/blob/master/src/Platform/AppleSpecialSemaphore.h - ARibeiro
这种方法的一个主要缺点是它不支持命名对象,例如sem_open所支持的,这些对象可以在进程之间使用。比如,在信号传递或简单IPC的情况下可以使用命名事件的替代方案。 - c00000fd

3
另一种选择可能是使用澳大利亚天文台软件组的 Keith Shortridge 的 sem_timedwait.c 实现。

源文件内容如下:

/*
*                       s e m _ t i m e d w a i t
*
*  Function:
*     Implements a version of sem_timedwait().
*
*  Description:
*     Not all systems implement sem_timedwait(), which is a version of
*     sem_wait() with a timeout. Mac OS X is one example, at least up to
*     and including version 10.6 (Leopard). If such a function is needed,
*     this code provides a reasonable implementation, which I think is
*     compatible with the standard version, although possibly less
*     efficient. It works by creating a thread that interrupts a normal
*     sem_wait() call after the specified timeout.
*
* ...
*
*  Limitations:
*
*     The mechanism used involves sending a SIGUSR2 signal to the thread
*     calling sem_timedwait(). The handler for this signal is set to a null
*     routine which does nothing, and with any flags for the signal 
*     (eg SA_RESTART) cleared. Note that this effective disabling of the
*     SIGUSR2 signal is a side-effect of using this routine, and means it
*     may not be a completely transparent plug-in replacement for a
*     'normal' sig_timedwait() call. Since OS X does not declare the
*     sem_timedwait() call in its standard include files, the relevant 
*     declaration (shown above in the man pages extract) will probably have
*     to be added to any code that uses this.
* 
* ...
* 
*  Copyright (c) Australian Astronomical Observatory.
*  Commercial use requires permission.
*  This code comes with absolutely no warranty of any kind.
*/

2
现在它位于https://github.com/attie/libxbee3/blob/master/xsys_darwin/sem_timedwait.c。 - Mike Crawford

2

2

1
请注意,此 mach 信号量不能通过共享内存在进程之间共享(只能通过 mach 消息传递)。 - heiner

1

我认为最简单的解决方案是将sem_wait()与调用alarm()结合使用,以唤醒并中止等待。例如:

alarm(2);
int return_value = sem_wait( &your_semaphore );
if( return_value == EINTR )
   printf( "we have been interrupted by the alarm." );

一个问题是闹钟以秒为单位输入,因此在您的情况下定时等待可能太长。

-- aghiles


0
在MacOS上,我建议基于SIGALRM的实现来设置以毫秒为单位的timeout
    struct itimerval value, old_value;

    value.it_interval.tv_sec = 0;
    value.it_interval.tv_usec = 0;
    value.it_value.tv_sec = timeout / 1000;
    value.it_value.tv_usec = (timeout - value.it_value.tv_sec * 1000) * 1000;

    setitimer(ITIMER_REAL, &value, &old_value);
    int status = sem_wait(communication->server_ready);
    setitimer(ITIMER_REAL, &old_value, NULL);
    SHM_LOG("communication_timedwaitfor_server -- END()\n");
    if (status < 0) {
        if (errno == EINTR) {
            /* TIMEOUT */
        } else {
            /* Error */
        }
    } else {
        /* unlock before timeout */
    }

0

如果您只能使用MP API:

  • MPCreateSemaphore / MPDeleteSemaphore
  • MPSignalSemaphore / MPWaitOnSemaphore

MPWaitOnSemaphore 如果超过指定的超时时间而没有信号,则存在kMPTimeoutErr


很遗憾,MultiProcessing API在10.7中已被弃用。 - MaddTheSane

0
你能否尝试通过在另一个线程中启动定时器,在计时器到期后调用sem_post()来模仿sem_timedwait()调用的功能,以防主线程没有调用sem_post()?

0
我原本计划使用以下函数作为替代,但后来发现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()是昂贵的。但启动线程也是昂贵的。

真正的解决方法是让苹果实现整个POSIX规范,而不仅仅是强制性部分。仅实现POSIX的强制性部分,然后声称符合POSIX标准,这只会让每个人都面临着半破碎的操作系统和大量的解决方法,如上述可能具有不理想的性能。

以上所有内容都说了,我放弃在Mac OSX和Linux上使用本地信号量(Sys V和POSIX)。它们在很多非常不幸的方式中都是有问题的。其他人也应该放弃它们。(我不会放弃这些操作系统上的信号量,只是本地实现。)无论如何,现在每个人都有一个sem_timedwait()实现,没有商业限制,其他人可以随意复制粘贴。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接