Как ждать и уведомлять, как в Java В C/C++ для общей памяти между двумя или более потоками? Я использую библиотеку pthread.
ждать и уведомлять в общей памяти C/C++
Ответы (6)
Вместо объекта Java, который вы использовали бы для ожидания/уведомления, вам нужны два объекта: мьютекс и переменная условия. Они инициализируются с помощью pthread_mutex_init
и pthread_cond_init
.
Там, где вы бы синхронизировались с объектом Java, используйте pthread_mutex_lock
и pthread_mutex_unlock
(обратите внимание, что в C вам нужно вручную связать их). Если вам не нужно ждать/уведомлять, просто блокировать/разблокировать, тогда вам не нужна условная переменная, только мьютекс. Имейте в виду, что мьютексы не обязательно являются «рекурсивными». Это означает, что если вы уже удерживаете блокировку, вы не можете снова взять ее, если не установите флаг инициализации, чтобы сказать, что вы хотите такого поведения.
Там, где вы бы позвонили 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++, для функций pthreads.
В своем заголовке вы так небрежно смешиваете C и C++ вместе в «C/C++». Я надеюсь, вы не пишете программу, которая представляет собой смесь этих двух.
Если вы используете С++ 11, вы найдете переносимую и (поскольку С++) гораздо более безопасную/простую в использовании альтернативу 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:
- этот код не содержит ни одного исходного указателя (, которые предательский)
- лямбда-выражения
- все виды другой синтаксической роскоши.
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 есть мьютексы, которые могут подойти и вам.