macos Mac OS X 上的 POSIX 信号量:sem_timedwait 替代方案

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/641126/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 06:04:22  来源:igfitidea点击:

POSIX Semaphores on Mac OS X: sem_timedwait alternative

macosunixposixsemaphore

提问by hhafez

I am trying to port a project (from linux) that uses Semaphores to Mac OS X however some of the posix semaphores are not implemented on Mac OS X

我正在尝试将一个使用信号量的项目(来自 linux)移植到 Mac OS X 但是一些 posix 信号量没有在 Mac OS X 上实现

The one that I hit in this port is sem_timedwait()

我在这个港口击中的那个是 sem_timedwait()

I don't know much about semaphores but from the man pages sem_wait()seems to be close to sem_timedwaitand it is implemented

我对信号量了解不多,但从手册页sem_wait()看来sem_timedwait,它似乎很接近并且已实现

From the man pages

从手册页

sem_timedwait()function shall lock the semaphore referenced by
semas in the sem_wait()function. However, if the semaphore cannot be
locked without waiting for another process or thread to unlock the
semaphore by performing a sem_post()function, this wait shall be ter-
minated when the specified timeout expires

sem_timedwait()函数应锁定
semas 在sem_wait()函数中引用的信号量。但是,如果在
不等待另一个进程或线程
通过执行sem_post()函数解锁信号量的情况下无法锁定信号量,则该等待将
在指定的超时到期时终止

From my limited understanding of how semphores work I can see that sem_timedwait()is safer, but I still should be able to use sem_wait()

从我对信号量如何工作的有限理解我可以看到它sem_timedwait()更安全,但我仍然应该能够使用 sem_wait()

Is this correct? If not what other alternatives do I have...

这个对吗?如果没有,我还有什么其他选择......

Thanks

谢谢

采纳答案by Jared Oberhaus

It's likely that the timeout is important to the operation of the algorithm. Therefore just using sem_wait()might not work.

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

You could use sem_trywait(), which returns right away in all cases. You can then loop, and use a sleep interval that you choose, each time decrementing the total timeout until you either run out of timeout or the semaphore is acquired.

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

A much better solution is to rewrite the algorithm to use a condition variable, and then you can use pthread_cond_timedwait()to get the appropriate timeout.

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

回答by Brian

I used to use named semaphores on OSX, but now sem_timedwait isn't available and sem_init and friends are deprecated. I implemented semaphores using pthread mutex and conditions as follows which work for me (OSX 10.13.1). You might have to make a handle vs struct table and look up the sem_t type if it can't hold a ptr in it (i.e. pointers are 64bits and sem_t is 32?)

我曾经在 OSX 上使用命名信号量,但现在 sem_timedwait 不可用并且不推荐使用 sem_init 和朋友。我使用 pthread mutex 和条件实现了信号量,如下所示,它们对我有用(OSX 10.13.1)。如果不能在其中保存 ptr,您可能需要创建一个句柄与结构表并查找 sem_t 类型(即指针是 64 位而 sem_t 是 32?)

#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

回答by chad

Yet another alternative may be to use the sem_timedwait.cimplementation by Keith Shortridge of the Australian Astronomical Observatory's software group.

另一种替代方法可能是使用 澳大利亚天文台软件组的 Keith Shortridge的sem_timedwait.c实现。

From the source file:

从源文件:

/*
*                       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.
*/

回答by flitzwald

Have you considered using the apache portable runtime? It's preinstalled on every Mac OS X Box and many Linux distros and it comes with a platform neutral wrapper around thread concurrency, that works even on MS Windows:

您是否考虑过使用 apache 便携式运行时?它预装在每个 Mac OS X Box 和许多 Linux 发行版上,并且带有一个围绕线程并发性的平台中立包装器,甚至可以在 MS Windows 上运行:

http://apr.apache.org/docs/apr/1.3/group__apr__thread__cond.html

http://apr.apache.org/docs/apr/1.3/group__apr__thread__cond.html

回答by stinkymatt

Could you try to mimic the functionality of the sem_timedwait() call by starting a timer in another thread that calls sem_post() after the timer expires if it hasn't been called by the primary thread that is supposed to call sem_post()?

如果没有被应该调用 sem_post() 的主线程调用,您能否尝试通过在另一个线程中启动一个计时器来模拟 sem_timedwait() 调用的功能,该计时器在计时器到期后调用 sem_post()?

回答by Aghiles

I think the simplest solution is to use sem_wait() in combination with a call to alarm() to wake up abort the wait. For example:

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

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

One issue is that alarm takes seconds as input so the timed wait might be too long in your case.

一个问题是警报需要几秒钟作为输入,因此在您的情况下,定时等待可能太长。

-- aghiles

——敏捷

回答by Valda

If you can just use MP API:

如果您可以只使用 MP API:

  • MPCreateSemaphore/MPDeleteSemaphore
  • MPSignalSemaphore/MPWaitOnSemaphore
  • MPCreateSemaphore/MPDeleteSemaphore
  • MPSignalSemaphore/MPWaitOnSemaphore

MPWaitOnSemaphoreexists with kMPTimeoutErrif specified timeout is exceeded without signaling.

MPWaitOnSemaphorekMPTimeoutErr如果超过指定的超时时间而没有发出信号,则存在。

回答by CubicleSoft

I was planning on using the following function as a replacement but then I discovered that sem_getvalue() was also deprecated and non-functional on OSX. You are free to use the following slightly untested code under a MIT or LGPL license (your choice).

我打算使用以下函数作为替代,但后来我发现 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 makes more sense than SIGUSR2 as another example here apparently uses (I didn't bother looking at it). SIGALRM is mostly reserved for alarm() calls, which are virtually useless when you want sub-second resolution.

SIGALRM 比 SIGUSR2 更有意义,因为这里显然使用了另一个示例(我没有费心看它)。SIGALRM 主要保留给 alarm() 调用,当您需要亚秒级分辨率时,这些调用实际上是无用的。

This code first attempts to acquire the semaphore with sem_trywait(). If that immediately succeeds, then it bails out. Otherwise, it starts a thread which is where the timer is implemented via pthread_cond_timedwait(). The MxSignaled boolean is used to determine the timeout state.

此代码首先尝试使用 sem_trywait() 获取信号量。如果这立即成功,那么它就会退出。否则,它会启动一个线程,该线程是通过 pthread_cond_timedwait() 实现计时器的地方。MxSignaled 布尔值用于确定超时状态。

You may also find this relevant function useful for calling the above sem_timedwait() implementation (again, MIT or LGPL, your choice):

您可能还会发现此相关函数对于调用上述 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
}

Helps populate a timespec structure with the closest thing to what clock_gettime() can provide. There are various comments out there that calling host_get_clock_service() repeatedly is expensive. But starting up a thread is also expensive.

帮助使用与 clock_gettime() 可以提供的最接近的内容填充 timespec 结构。有各种各样的评论说重复调用 host_get_clock_service() 很昂贵。但是启动线程也很昂贵。

The real fix is for Apple to implement the entire POSIX specification, not just the mandatory parts. Implementing only the mandatory bits of POSIX and then claiming POSIX compliance just leaves everyone with a half-broken OS and tons of workarounds like the above that may have less-than-ideal performance.

真正的解决方法是让 Apple 实现整个 POSIX 规范,而不仅仅是强制部分。仅实现 POSIX 的强制性位,然后声称符合 POSIX 只会让每个人的操作系统都半途而废,并且像上面这样的大量变通方法可能具有不太理想的性能。

The above all said, I am giving up on native semaphores (both Sys V and POSIX) on both Mac OSX and Linux. They are broken in quite a few rather unfortunate ways. Everyone else should give up on them too. (I'm not giving up on semaphores on those OSes, just the native implementations.) At any rate, now everyone has a sem_timedwait() implementation without commercial restrictions that others can copy-pasta to their heart's content.

综上所述,我放弃了 Mac OSX 和 Linux 上的本机信号量(包括 Sys V 和 POSIX)。它们以很多相当不幸的方式被破坏。其他人也应该放弃他们。(我并没有放弃那些操作系统上的信号量,只是本机实现。)无论如何,现在每个人都有一个没有商业限制的 sem_timedwait() 实现,其他人可以将意大利面复制到他们的心思内容。

回答by gavv

One option is to use low-level semaphore mach API:

一种选择是使用低级信号量 mach API:

#include <mach/semaphore.h>

semaphore_create(...)

semaphore_wait(...)
semaphore_timedwait(...)
semaphore_signal(...)

semaphore_destroy(...)

It is used in libuv BTW.

它用于 libuv BTW。

Reference:

参考: