Ждать и уведомлять в общей памяти C/C++
Как ждать и уведомлять, как в Java В C/C++ для разделяемой памяти между двумя или более потоков? Я использую библиотеку pthread.
6 ответов
Вместо Java-объекта, который вы бы использовали для ожидания / уведомления, вам нужны два объекта: мьютекс и переменная условия. Они инициализируются с pthread_mutex_init
а также pthread_cond_init
,
Где бы вы синхронизировались на объекте Java, используйте pthread_mutex_lock
а также pthread_mutex_unlock
(обратите внимание, что в C вы должны соединить их вручную). Если вам не нужно ждать / уведомлять, просто блокировать / разблокировать, тогда вам не нужна переменная условия, только мьютекс. Имейте в виду, что мьютексы не обязательно являются "рекурсивными". Это означает, что если вы уже удерживаете блокировку, вы не сможете снять ее снова, если не установите флаг init, чтобы сказать, что вы хотите такое поведение.
Куда бы вы позвонили java.lang.Object.wait
, вызов pthread_cond_wait
или же pthread_cond_timedwait
,
Куда бы вы позвонили java.lang.Object.notify
, вызов pthread_cond_signal
,
Куда бы вы позвонили java.lang.Object.notifyAll
, вызов pthread_cond_broadcast
,
Как и в Java, ложные пробуждения возможны из функций ожидания, поэтому вам нужно некоторое условие, которое устанавливается перед вызовом для сигнала и проверяется после вызова, чтобы ждать, и вам нужно вызвать pthread_cond_wait
в петле. Как и в Java, мьютекс освобождается, пока вы ждете.
В отличие от Java, где вы не можете позвонить notify
если вы не держите монитор, вы можете позвонить pthread_cond_signal
без удержания мьютекса. Однако обычно он ничего вам не приносит, и часто это очень плохая идея (потому что обычно вы хотите заблокировать - установить условие - сигнал - разблокировать). Так что лучше просто игнорировать это и относиться к нему как к Java.
В этом нет ничего особенного, основной шаблон такой же, как Java, и не случайно. Тем не менее, прочитайте документацию по всем этим функциям, потому что есть различные флаги и забавные поведения, о которых вы хотите знать и / или избегать.
В C++ вы можете сделать это немного лучше, чем просто с помощью API pthreads. Вы должны по крайней мере применить RAII к блокировке / разблокировке мьютекса, но в зависимости от того, какие библиотеки C++ вы можете использовать, вам лучше использовать более обёртку C++-ish для функций pthreads.
В своем названии вы случайно смешиваете C и C++ в "C/C++". Надеюсь, вы не пишете программу, которая является смесью двух.
Если вы используете C++11, вы найдете портативную и (потому что C++, так) гораздо более безопасную / более простую в использовании альтернативу pthreads (в системах POSIX обычно используется pthreads под капотом).
Ты можешь использовать std::condition_variable
+ std::mutex
для ожидания / уведомления. Этот пример показывает, как:
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex m;
std::condition_variable cv;
std::string data;
bool mainReady = false;
bool workerReader = false;
void worker_thread()
{
// Wait until main() sends data
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return mainReady;});
}
std::cout << "Worker thread is processing data: " << data << std::endl;
data += " after processing";
// Send data back to main()
{
std::lock_guard<std::mutex> lk(m);
workerReady = true;
std::cout << "Worker thread signals data processing completed\n";
}
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";
// send data to the worker thread
{
std::lock_guard<std::mutex> lk(m);
mainReady = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return workerReady;});
}
std::cout << "Back in main(), data = " << data << '\n';
// wait until worker dies finishes execution
worker.join();
}
Этот код также подчеркивает некоторые другие сильные стороны C++ по сравнению с C:
- этот код не содержит ни одного необработанного указателя ( которые являются коварными)
- лямбда-выражения
- все виды других синтаксических swagg.
pthread_cond_wait и pthread_cond_signal могут использоваться для синхронизации на основе условия
Использование условных переменных является одним из способов сделать это: они доступны при использовании pthread
библиотека под Linux (см. ссылку).
Условная переменная является переменной типа pthread_cond_t и используется с соответствующими функциями для ожидания и последующего продолжения процесса.
Если вас не волнует переносимость, Linux предлагает eventfd, который дает вам именно то, что вы хотите. Каждый eventfd хранит внутренний счетчик. В режиме по умолчанию чтение из блоков eventfd, если счетчик равен нулю, в противном случае возвращается немедленно. Запись в него добавит к внутреннему счетчику.
Ожидание вызова, таким образом, будет просто uint64_t buf_a; read(event_fd, &buf_a, sizeof(buf_a));
где buf должен быть 8-байтовым буфером. Чтобы уведомить ожидающую ветку, вы должны сделать uint64_t buf_b = 1; write(event_fd, &buf_b, sizeof(buf_b));
,
Если возможно, вы можете использовать семафоры POSIX. В библиотеке pthread есть мьютексы, которые также могут работать на вас.