繁体   English   中英

sem_wait()无法在Linux上唤醒

[英]sem_wait() failed to wake up on linux

我有一个使用共享FIFO的实时应用程序。 有多个编写程序和一个读取程序。 数据定期写入FIFO中并不断消耗。 从理论上讲,FIFO永远不会溢出,因为读取速度快于所有写入器的总和。 但是,FIFO确实溢出。

我试图重现该问题,并最终得出以下(简化)代码:

#include <stdint.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <pthread.h>
#include <semaphore.h>
#include <sys/time.h>
#include <unistd.h>


class Fifo
{
public:
    Fifo() : _deq(0), _wptr(0), _rptr(0), _lock(0)
    {
        memset(_data, 0, sizeof(_data));
        sem_init(&_data_avail, 1, 0);
    }

    ~Fifo()
    {
        sem_destroy(&_data_avail);
    }

    void Enqueue()
    {
        struct timeval tv;
        gettimeofday(&tv, NULL);
        uint64_t enq = tv.tv_usec + tv.tv_sec * 1000000;
        while (__sync_lock_test_and_set(&_lock, 1))
            sched_yield();
        uint8_t wptr = _wptr;
        uint8_t next_wptr = (wptr + 1) % c_entries;
        int retry = 0;
        while (next_wptr == _rptr)      // will become full
        {
            printf("retry=%u enq=%lu deq=%lu count=%d\n", retry, enq, _deq, Count());
            for (uint8_t i = _rptr; i != _wptr; i = (i+1)%c_entries)
                printf("%u: %lu\n", i, _data[i]);
            assert(retry++ < 2);
            usleep(500);
        }
        assert(__sync_bool_compare_and_swap(&_wptr, wptr, next_wptr));
        _data[wptr] = enq;
        __sync_lock_release(&_lock);
        sem_post(&_data_avail);
    }

    int Dequeue()
    {
        struct timeval tv;
        gettimeofday(&tv, NULL);
        uint64_t deq = tv.tv_usec + tv.tv_sec * 1000000;
        _deq = deq;
        uint8_t rptr = _rptr, wptr = _wptr;
        uint8_t next_rptr = (rptr + 1) % c_entries;
        bool empty = Count() == 0;
        assert(!sem_wait(&_data_avail));// bug in sem_wait?
        _deq = 0;
        uint64_t enq = _data[rptr];     // enqueue time
        assert(__sync_bool_compare_and_swap(&_rptr, rptr, next_rptr));
        int latency = deq - enq;        // latency from enqueue to dequeue
        if (empty && latency < -500)
        {
            printf("before dequeue: w=%u r=%u; after dequeue: w=%u r=%u; %d\n", wptr, rptr, _wptr, _rptr, latency);
        }
        return latency;
    }

    int Count()
    {
        int count = 0;
        assert(!sem_getvalue(&_data_avail, &count));
        return count;
    }

    static const unsigned c_entries = 16;

private:
    sem_t _data_avail;
    uint64_t _data[c_entries];
    volatile uint64_t _deq;     // non-0 indicates when dequeue happened
    volatile uint8_t _wptr, _rptr;      // write, read pointers
    volatile uint8_t _lock;     // write lock
};


static const unsigned c_total = 10000000;
static const unsigned c_writers = 3;

static Fifo s_fifo;


// writer thread
void* Writer(void* arg)
{
    for (unsigned i = 0; i < c_total; i++)
    {
        int t = rand() % 200 + 200;     // [200, 399]
        usleep(t);
        s_fifo.Enqueue();
    }
    return NULL;
}

int main()
{
    pthread_t thread[c_writers];
    for (unsigned i = 0; i < c_writers; i++)
        pthread_create(&thread[i], NULL, Writer, NULL);

    for (unsigned total = 0; total < c_total*c_writers; total++)
        s_fifo.Dequeue();
}

当Enqueue()溢出时,调试打印将指示Dequeue()被卡住(因为_deq不为0)。 Dequeue()可能卡住的唯一地方是sem_wait()。 但是,由于fifo已满(也已由sem_getvalue()确认),所以我不知道该怎么办。 即使经过几次重试(每次等待500us),即使Dequeue()在Enqueue()完全停止(忙重试)时肯定会耗尽,fifo仍然充满。

在代码示例中,有3位作家,每位作家每200-400us。 在我的计算机上(运行centOS 6.5内核2.6.32-279.22.1.el6.x86_64,g ++ 4.47 20120313的8核i7-2860),代码将在几分钟后失败。 我还在其他几个centOS系统上进行了尝试,但同样失败。

我知道增大fifo可以减少溢出的可能性(实际上,程序仍然会失败,但c_entries = 128),但是在我的实时应用程序中,入队出队等待时间有严格的限制,因此必须快速耗尽数据。 如果它不是sem_wait()中的错误,那么是什么阻止了它获取信号量?

PS如果我更换

        assert(!sem_wait(&_data_avail));// bug in sem_wait?

        while (sem_trywait(&_data_avail) < 0) sched_yield();

然后程序运行正常。 因此,似乎sem_wait()和/或调度程序中存在问题。

您需要结合使用sem_wait / sem_post调用才能管理您的读写线程。

您的入队线程仅执行sem_post,而出队线程仅执行sem_wait调用。 您需要将sem_wait添加到入队线程,并在出队线程上添加sem_post。

很久以前,我实现了使多个线程/进程能够读取某些共享内存并且仅一个线程/进程写入共享内存的功能。 我使用了两个信号灯,一个写信号灯和一个读信号灯。 读线程将等待,直到未设置写信号量,然后才设置读信号量。 写线程将设置写信号量,然后等待直到未设置读信号量。 然后,读写线程将在完成任务后取消设置信号量。 读信号量可以一次有n个线程锁定读取信号量,而写信号量可以一次由一个线程锁定。

如果它不是sem_wait()中的错误,那么是什么阻止了它获取信号量?

程序的不耐烦会阻止它。 不能保证Dequeue()线程在给定的重试次数内进行调度。 如果你改变

            assert(retry++ < 2);

            retry++;

您会发现该程序有时在8次甚至更多次重试后会愉快地继续读取程序。

为什么入队必须重试?

它必须重试是因为当时尚未安排main线程的Dequeue()

出队速度比所有编写器的总和要快得多。

您的程序显示此假设有时是错误的。 尽管显然Dequeue()的执行时间比写程序的执行时间要短得多(由于usleep(t) ),但这并不意味着Dequeue()由完全公平调度程序调度的频率更高-为此,原因是您使用了不确定的调度策略。 man sched_yield

sched_yield() is intended for use with read-time scheduling policies
       (i.e., SCHED_FIFO or SCHED_RR).  Use of sched_yield() with
       nondeterministic scheduling policies such as SCHED_OTHER is
       unspecified and very likely means your application design is broken.

如果插入

    struct sched_param param = { .sched_priority = 1 };
    if (sched_setscheduler(0, SCHED_FIFO, &param) < 0)
        perror("sched_setscheduler");

main()的开始处,您可能会看到您的程序按预期方式运行(以适当的特权运行时)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM