Polling: Event Driven Programming#

Threads Are Expensive#

  • Each thread requires its own stack

    • Too big ⟶ waste

    • Too small ⟶ undefined behavior

    • Too many ⟶ bad by nature

  • Context switches are not free

#include <why.h>
#include <why-thread.h>
#include <why-messagequeue.h>
#include <why-irq.h>
#include <why-time.h>

int main()
{
    Why::init();

    Why::MessageQueue<int> q1(7);
    Why::MessageQueue<int> q2(5);

    Why::IRQ::connect(7,  [&q1](int){auto ok = q1.put_noblock(1); assert(ok);});
    Why::IRQ::connect(42, [&q2](int){auto ok = q2.put_noblock(2); assert(ok);});

    Why::Thread t1([&q1](){                            // <-- stack memory usage
        while (true) {
            int elem = q1.get();                       // <-- blocks
            std::println("t1: {}", elem);
        }
    });
    Why::Thread t2([&q2](){                            // <-- stack memory usage
        while (true) {
            int elem = q2.get();                       // <-- blocks
            std::println("t2: {}", elem);
        }
    });

    return 0;
}

Polling Is Expensive#

  • Alternative: collapse into one single thread

  • ⟶ How to block on two different message queues in one single thread?

  • Naive answer: don’t block - poll!

#include <why.h>
#include <why-messagequeue.h>
#include <why-irq.h>

int main()
{
    Why::init();

    Why::MessageQueue<int> q1(7);
    Why::MessageQueue<int> q2(5);

    Why::IRQ::connect(7,  [&q1](int){auto ok = q1.put_noblock(1); assert(ok);});
    Why::IRQ::connect(42, [&q2](int){auto ok = q2.put_noblock(2); assert(ok);});

    while (true) {
        auto elem1 = q1.get_noblock();
        if (elem1)
            std::println("q1: {}", *elem1);

        auto elem2 = q2.get_noblock();
        if (elem2)
            std::println("q2: {}", *elem2);
    }

    return 0;
}

Polling Made Cool#

  • Polling API (much like Linux/UNIX)

  • May block until one or more kernel objects are ready

  • Zephyr (see Zephyr: Polling API)

    • A semaphore becomes available

    • A kernel FIFO contains data ready to be retrieved

    • A kernel LIFO contains data ready to be retrieved

    • A kernel message queue contains data ready to be retrieved

    • A kernel pipe contains data ready to be retrieved

    • A poll signal is raised

  • Linux: anything that can be represented by a file descriptor

#include <why.h>
#include <why-messagequeue.h>
#include <why-irq.h>
#include <why-eventloop.h>

int main()
{
    Why::init();

    Why::MessageQueue<int> q1(7);
    Why::MessageQueue<int> q2(5);

    Why::IRQ::connect(7,  [&q1](int){auto ok = q1.put_noblock(1); assert(ok);});
    Why::IRQ::connect(42, [&q2](int){auto ok = q2.put_noblock(2); assert(ok);});

    Why::Eventloop loop;
    loop.watch(q1, [&q1](){
        int elem = q1.get();                           // <-- does not block
        std::println("q1: {}", elem);
    });
    loop.watch(q2, [&q2](){
        int elem = q2.get();                           // <-- does not block
        std::println("q2: {}", elem);
    });

    loop();                                            // <-- blocks
    return 0;
}