简体   繁体   English

sem_wait()无法在Linux上唤醒

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

I have a real-time application that uses a shared FIFO. 我有一个使用共享FIFO的实时应用程序。 There are several writer processes and one reader process. 有多个编写程序和一个读取程序。 Data is periodically written into the FIFO and constantly drained. 数据定期写入FIFO中并不断消耗。 Theoretically the FIFO should never overflow because the reading speed is faster than all writers combined. 从理论上讲,FIFO永远不会溢出,因为读取速度快于所有写入器的总和。 However, the FIFO does overflow. 但是,FIFO确实溢出。

I tried to reproduce the problem and finally worked out the following (simplified) code: 我试图重现该问题,并最终得出以下(简化)代码:

#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();
}

When Enqueue() overflows, the debug print indicates that Dequeue() is stuck (because _deq is not 0). 当Enqueue()溢出时,调试打印将指示Dequeue()被卡住(因为_deq不为0)。 The only place where Dequeue() can get stuck is sem_wait(). Dequeue()可能卡住的唯一地方是sem_wait()。 However, since the fifo is full (also confirmed by sem_getvalue()), I don't understand how that could happen. 但是,由于fifo已满(也已由sem_getvalue()确认),所以我不知道该怎么办。 Even after several retries (each waits 500us) the fifo was still full even though Dequeue() should definitely drain while Enqueue() is completely stopped (busy retrying). 即使经过几次重试(每次等待500us),即使Dequeue()在Enqueue()完全停止(忙重试)时肯定会耗尽,fifo仍然充满。

In the code example, there are 3 writers, each writing every 200-400us. 在代码示例中,有3位作家,每位作家每200-400us。 On my computer (8-core i7-2860 running centOS 6.5 kernel 2.6.32-279.22.1.el6.x86_64, g++ 4.47 20120313), the code would fail in a few minutes. 在我的计算机上(运行centOS 6.5内核2.6.32-279.22.1.el6.x86_64,g ++ 4.47 20120313的8核i7-2860),代码将在几分钟后失败。 I also tried on several other centOS systems and it also failed the same way. 我还在其他几个centOS系统上进行了尝试,但同样失败。

I know that making the fifo bigger can reduce overflow probability (in fact, the program still fails with c_entries=128), but in my real-time application there is hard constraint on enqueue-dequeue latency, so data must be drained quickly. 我知道增大fifo可以减少溢出的可能性(实际上,程序仍然会失败,但c_entries = 128),但是在我的实时应用程序中,入队出队等待时间有严格的限制,因此必须快速耗尽数据。 If it's not a bug in sem_wait(), then what prevents it from getting the semaphore? 如果它不是sem_wait()中的错误,那么是什么阻止了它获取信号量?

PS If I replace PS如果我更换

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

with

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

then the program runs fine. 然后程序运行正常。 So it seems that there's something wrong in sem_wait() and/or scheduler. 因此,似乎sem_wait()和/或调度程序中存在问题。

You need to use a combination of sem_wait/sem_post calls to be able to manage your read and write threads. 您需要结合使用sem_wait / sem_post调用才能管理您的读写线程。

Your enqueue thread performs a sem_post only and your dequeue performs sem_wait only call. 您的入队线程仅执行sem_post,而出队线程仅执行sem_wait调用。 you need to add sem_wait to the enqueue thread and a sem_post on the dequeue thread. 您需要将sem_wait添加到入队线程,并在出队线程上添加sem_post。

A long time ago, I implemented the ability to have multiple threads/process be able to read some shared memory and only one thread/process write to the shared memory. 很久以前,我实现了使多个线程/进程能够读取某些共享内存并且仅一个线程/进程写入共享内存的功能。 I used two semaphore, a write semaphore and a read semaphore. 我使用了两个信号灯,一个写信号灯和一个读信号灯。 The read threads would wait until the write semaphore was not set and then it would set the read semaphore. 读线程将等待,直到未设置写信号量,然后才设置读信号量。 The write threads would set the write semaphore and then wait until the read semaphore is not set. 写线程将设置写信号量,然后等待直到未设置读信号量。 The read and write threads would then unset the set semaphores when they've completed their tasks. 然后,读写线程将在完成任务后取消设置信号量。 The read semaphore can have n threads lock the read semaphore at a time while the write semaphore can be lock by a single thread at a time. 读信号量可以一次有n个线程锁定读取信号量,而写信号量可以一次由一个线程锁定。

If it's not a bug in sem_wait(), then what prevents it from getting the semaphore? 如果它不是sem_wait()中的错误,那么是什么阻止了它获取信号量?

Your program's impatience prevents it. 程序的不耐烦会阻止它。 There is no guarantee that the Dequeue() thread is scheduled within a given number of retries. 不能保证Dequeue()线程在给定的重试次数内进行调度。 If you change 如果你改变

            assert(retry++ < 2);

to

            retry++;

you'll see that the program happily continues the reader process sometimes after 8 or perhaps even more retries. 您会发现该程序有时在8次甚至更多次重试后会愉快地继续读取程序。

Why does Enqueue have to retry? 为什么入队必须重试?

It has to retry simply because the main thread's Dequeue() hasn't been scheduled by then. 它必须重试是因为当时尚未安排main线程的Dequeue()

Dequeue speed is much faster than all writers combined. 出队速度比所有编写器的总和要快得多。

Your program shows that this assumption is sometimes false. 您的程序显示此假设有时是错误的。 While apparently the execution time of Dequeue() is much shorter than that of the writers (due to the usleep(t) ), this does not imply that Dequeue() is scheduled by the Completely Fair Scheduler more often - and for this the main reason is that you used a nondeterministic scheduling policy. 尽管显然Dequeue()的执行时间比写程序的执行时间要短得多(由于usleep(t) ),但这并不意味着Dequeue()由完全公平调度程序调度的频率更高-为此,原因是您使用了不确定的调度策略。 man sched_yield : 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.

If you insert 如果插入

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

at the start of main() , you'll likely see that your program performs as expected (when run with the appropriate priviledge). main()的开始处,您可能会看到您的程序按预期方式运行(以适当的特权运行时)。

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

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