Issue
This question is for Linux.
I am looking for an alternative for Windows EVENT for Linux.
I have several threads, which perform task + sleep in a loop.
If system is shutting-down I want to signal all threads to exit.
I know about condition-variables
, but I do not need a mutex
.
I also do not want to introduce a mutex for these free threads.
So pseudo code will be:
void WorkerThreads()
{
do
{
...do something...
WaitForEvent(event, timeout);
if (event happened)
return;
} while (not shutting down);
}
main-thread()
{
...start threads...
SetEvent(shutdown);
}
Solution
If your code doesn't need to be portable to non-Linux UNIXes (such as macOS, FreeBSD), you can use eventfd
as a direct replacement for Windows event handles, and poll
(or epoll
if you need something more complicated) as a direct replacement for WaitForSingleObject
. (On Linux, file descriptors are the equivalent of Windows API HANDLE
s. They are int
instead of a pointer, but otherwise they work in a very similar manner.)
The following example program shows how eventfd
can be used when waiting for a single event:
#include <sys/eventfd.h>
#include <poll.h>
#include <errno.h>
#include <unistd.h>
#include <iostream>
#include <thread>
#include <chrono>
#include <cstring>
#include <cstdint>
#include <functional>
// Helper functions
// These somewhat mimic Win32 API, but instead of GetLastError() use errno.
// These kinds of events are always manual reset
// return value < 0 -> error
static int createEvent(bool initialState)
{
return eventfd(initialState ? 1 : 0, EFD_CLOEXEC | EFD_NONBLOCK);
}
static bool resetEvent(int eventFd)
{
// eventfd uses 64bit integers for signalling
uint64_t dummy = 0;
ssize_t l = read(eventFd, &dummy, sizeof(dummy));
if (l < 0 && errno != EAGAIN)
return false;
// EAGAIN is non-fatal here since that just means that
// the event was already reset when we were called,
// which is ok
return true;
}
static bool setEvent(int eventFd)
{
// eventfd uses 64bit integers for signalling
uint64_t dummy = 1;
ssize_t l = write(eventFd, &dummy, sizeof(dummy));
if (l < 0)
return false;
return true;
}
// timeoutMs < 0: infinite
// timeoutMs > 0: timeout in ms
// timeoutMs == 0: just checks if event was set
// return value: true if event set,
// false if not within timeout (errno == 0)
// false if error (errno != 0)
//
// this is just a simple function that waits for a single
// event; you can obviously also use poll/epoll
// to wait for multiple events if you need to,
// and combine that with waiting for data on
// sockets and similar
static bool waitForEvent(int eventFd, int timeoutMs)
{
struct pollfd pfd;
int rc = -1;
errno = EINTR;
while (rc < 0 && errno == EINTR) {
pfd.fd = eventFd;
pfd.events = POLLIN;
pfd.revents = 0;
rc = poll(&pfd, 1, timeoutMs);
// technically we should recalculate the
// timeoutMs if errno == EINTR here, but
// since EINTR is a very rare condition,
// keep it simple at this point
}
if (rc < 0)
return false;
if (rc == 0 || !(pfd.revents & POLLIN)) {
errno = 0;
return false;
}
return true;
}
// End of helper functions
void threadMain(int eventFd)
{
std::cout << "Separate thread: sleeping for 5s...\n" << std::flush;
std::this_thread::sleep_for(std::chrono::seconds{5});
std::cout << "Separate thread: setting event...\n" << std::flush;
setEvent(eventFd);
std::cout << "Separate thread: exiting...\n" << std::flush;
}
int main()
{
int eventFd = createEvent(false);
if (eventFd < 0) {
int error = errno;
std::cerr << "Error creating event fd: " << strerror(error) << std::endl;
return 1;
}
auto thread = std::thread(std::bind(&threadMain, eventFd));
// wait forever for event
bool ok = waitForEvent(eventFd, -1);
if (!ok) {
// since we have infinite timeout this will only
// happen on error (but if a finite timeout is
// specified this may happen when that occurs)
int error = errno;
std::cerr << "Error waiting for event: " << strerror(error) << std::endl;
thread.join();
return 2;
}
resetEvent(eventFd);
std::cout << "Successfully waited for event, and the event was reset.\n" << std::flush;
// Check that the event is no longer set
// (this is just for demonstration purposes)
ok = waitForEvent(eventFd, 0);
if (ok)
std::cerr << "Warning: the event is still set after being reset\n" << std::flush;
// Clean up stuff
thread.join();
return 0;
}
A couple of notes regarding the choice of flags: the code above uses both EFD_CLOEXEC
and EFD_NONBLOCK
to create the eventfd. EFD_CLOEXEC
is so that the file descriptor isn't inherited by other programs that are launched by your program, and EFD_NONBLOCK
sets the eventfd to non-blocking mode, to be able to create something that resembles the semantics of the Win32 API functions. If you remove EFD_NONBLOCK
then read()
from the eventfd (as used in the resetEvent()
helper in above code) becomes blocking, and then there's no direct mapping to the semantics of the Win32 API functions, but it may be useful in other cases. (You could create auto-reset Win32 events with that, but only if you only ever wait for single events via a blocking read()
; waiting for multiple events always requires poll()
/epoll()
, and those alone will never reset an eventfd.)
If you want to be portable with UNIX systems other than Linux, you can always use a pipe()
, but that's two file descriptors (one for writing, i.e. "setting" the event, one for reading, i.e. "resetting" and waiting for the event).
Answered By - chris_se Answer Checked By - Pedro (WPSolving Volunteer)