Есть ли более простое решение для переноса события ручного сброса Windows в pthread, чем условная переменная pthread + мьютекс pthread + флаг, если событие установлено или не установлено?





Pthreads - это конструкции низкого уровня. Нет, нет более простого механизма; pthread_cond__* концептуально аналогичен событию автоматического сброса. Будьте осторожны, pthread_cond_wait может вызывать ложное срабатывание, поэтому его никогда не следует использовать без какого-либо внешнего флага, независимо от ситуации.
Однако построить собственное будет не так уж сложно.
#include <pthread.h>
#include <stdbool.h>
struct mrevent {
pthread_mutex_t mutex;
pthread_cond_t cond;
bool triggered;
};
void mrevent_init(struct mrevent *ev) {
pthread_mutex_init(&ev->mutex, 0);
pthread_cond_init(&ev->cond, 0);
ev->triggered = false;
}
void mrevent_trigger(struct mrevent *ev) {
pthread_mutex_lock(&ev->mutex);
ev->triggered = true;
pthread_cond_signal(&ev->cond);
pthread_mutex_unlock(&ev->mutex);
}
void mrevent_reset(struct mrevent *ev) {
pthread_mutex_lock(&ev->mutex);
ev->triggered = false;
pthread_mutex_unlock(&ev->mutex);
}
void mrevent_wait(struct mrevent *ev) {
pthread_mutex_lock(&ev->mutex);
while (!ev->triggered)
pthread_cond_wait(&ev->cond, &ev->mutex);
pthread_mutex_unlock(&ev->mutex);
}
Это может не соответствовать вашему использованию, так как у вас часто будет другой замок, который вы захотите использовать вместо ev->mutex, но это суть того, как он обычно используется.
@ephemient: «Функции pthread_cond_wait () и pthread_cond_timedwait () используются для блокировки переменной условия. Они вызываются с мьютексом, заблокированным вызывающим потоком, в противном случае будет неопределенное поведение». Разве вы не должны захватывать мьютекс перед каждым вызовом pthread_cond_wait? Эти функции атомарно освобождают мьютекс и заставляют вызывающий поток блокироваться по условной переменной cond;
@ user877329: Функции pthread_cond_ * отбрасывают мьютекс во время ожидания и повторно захватывают его перед возвратом.
@ephemient отличный ответ человек. Сработало для меня как шарм. определенно +1. Я бы принял ответ, если бы спросил. Я не знаю, что не так с sfhdhsf, поскольку это отлично отвечает на его вопрос :)
Я думаю, что события Windows больше похожи на семафор. Т.е. для автоматического сброса вы должны использовать двоичный семафор и функцию sem_timedwait ().
Нет более простого решения, но следующий код поможет:
void LinuxEvent::wait()
{
pthread_mutex_lock(&mutex);
int signalValue = signalCounter;
while (!signaled && signalValue == signalCounter)
{
pthread_cond_wait(&condition, &mutex);
}
pthread_mutex_unlock(&mutex);
}
void LinuxEvent::signal()
{
pthread_mutex_lock(&mutex);
signaled = true;
signalCounter++;
pthread_cond_broadcast(&condition);
pthread_mutex_unlock(&mutex);
}
void LinuxEvent::reset()
{
pthread_mutex_lock(&mutex);
signaled = false;
pthread_mutex_unlock(&mutex);
}
При вызове signal () событие переходит в сигнальное состояние, и весь ожидающий поток запускается. Тогда событие останется в сигнальном состоянии, и весь поток, вызывающий wait (), не будет ждать. Вызов reset () вернет событие в несигнальное состояние.
SignalCounter есть на тот случай, если вы выполните быстрый сигнал / сброс, чтобы разбудить ожидающие потоки.
Вы можете легко реализовать события с ручным сбросом с помощью каналов:
событие находится в запущенном состоянии -> есть что прочитать из канала
SetEvent -> написать ()
ResetEvent -> читать ()
WaitForMultipleObjects -> poll () (или select ()) для чтения
операция «SetEvent» должна записать что-то (например, 1 байт любого значения) только для того, чтобы перевести канал в непустое состояние, поэтому последующая операция «Ожидание», то есть poll () для данных, доступных для чтения, не будет блокироваться.
Операция «ResetEvent» считывает записанные данные, чтобы убедиться, что канал снова пуст. Конец чтения канала должен быть неблокирующим, чтобы попытка сбросить (прочитать из) уже сбросить событие (пустой канал) не блокировала - fcntl (pipe_out, F_SETFL, O_NONBLOCK) Поскольку перед ResetEvent может быть более 1 SetEvents, вы должны закодировать его так, чтобы он читал столько байтов, сколько есть в конвейере:
char buf[256]; // 256 is arbitrary
while( read(pipe_out, buf, sizeof(buf)) == sizeof(buf));
Обратите внимание, что ожидание события не считывается из конвейера, и, следовательно, «событие» останется в запущенном состоянии до операции сброса.
Я предпочитаю конвейерный подход, потому что часто нужно ждать не просто события, а нескольких объектов, например. WaitForMultipleObjects(...). А с помощью каналов можно легко заменить вызов Windows WaitForMultipleObjects на poll(...), select, pselect и epoll.
Существовал облегченный метод синхронизации процессов под названием Futex (системный вызов Fast Userspace Locking). Была функция futex_fd для получения одного или нескольких файловых дескрипторов для фьютексов. Этот файловый дескриптор вместе с, возможно, многими другими, представляющими реальные файлы, устройства, сокеты и т.п., может быть передан в select, poll или epoll. К сожалению, это был удаленный из ядра. Так что уловки с трубами остаются единственным средством для этого:
int pipefd[2];
char buf[256]; // 256 is arbitrary
int r = pipe2(pipefd, O_NONBLOCK);
void setEvent()
{
write(pipefd[1], &buf, 1);
}
void resetEvent() { while( read(pipefd[0], &buf, sizeof(buf)) > 0 ) {;} }
void waitForEvent(int timeoutMS)
{
struct pollfd fds[1];
fds[0].fd = pipefd[0];
fds[0].events = POLLRDNORM;
poll(fds, 1, timeoutMS);
}
// finalize:
close(pipefd[0]);
close(pipefd[1]);
Мы искали аналогичное решение для переноса многопоточного кода C++ из Windows в Linux и в итоге написали Библиотека событий Win32 для Linux с открытым исходным кодом под лицензией MIT. Это должно быть то решение, которое вы ищете, и оно было тщательно проверено с точки зрения производительности и потребления ресурсов.
В нем реализованы события ручного и автоматического сброса, а также функции WaitForSingleObject и WaitForMultipleObject.
Выглядит очень красиво. Его слишком плохие дескрипторы * nix, семафоры, события, потоки, сокеты и т. д. Не ждут единообразно, как в Windows, где ими управляет один вызов.
Мы (полное раскрытие: я работаю в NeoSmart Technologies) написали библиотеку с открытым исходным кодом (под лицензией MIT) под названием Pevents, которая реализует события ручного и автоматического сброса WIN32 на POSIX, и включает в себя клоны WaitForSingleObject и WaitForMultipleObjects. С тех пор он получил некоторое распространение (он используется в Steam на Linux / Mac) и работает довольно хорошо.
Хотя я лично советую вам использовать парадигмы многопоточности и сигнализации POSIX при кодировании на машинах POSIX, pevents дает вам другой выбор, если он вам нужен.
Не забывайте, что событие автоматического сброса Windows «запомнит», что оно было отправлено, и проинформирует следующий ожидающий поток, а затем сбросит себя. pthread_cond_signal может фактически ничего не делать, если нет ожидающих потоков, поэтому "событие" в этом случае могло бы показаться, что не произошло.