ждать и уведомлять в общей памяти C/C++

Как ждать и уведомлять, как в Java В C/C++ для общей памяти между двумя или более потоками? Я использую библиотеку pthread.


person Sajad Bahmani    schedule 18.01.2010    source источник


Ответы (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.

person Steve Jessop    schedule 18.01.2010

В своем заголовке вы так небрежно смешиваете 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:

  1. этот код не содержит ни одного исходного указателя (, которые предательский)
  2. лямбда-выражения
  3. все виды другой синтаксической роскоши.
person Domi    schedule 06.12.2013
comment
Я надеюсь, вы не пишете программу, которая представляет собой смесь двух, в чем проблема их смешивания? - person Michel Feinstein; 27.09.2019
comment
@mFeinstein На практике вы часто их смешиваете. Однако, когда вы сомневаетесь, думая о ... Должен ли я использовать необработанный указатель или интеллектуальный указатель?, вы уже используете C++ (потому что C не имеет интеллектуальных указателей), поэтому вы определенно хотите использовать интеллектуальные указатели, если нет есть какой-то API или другие ограничения, запрещающие их использование или они явно не нужны и т. д. Если вы не принимаете это решение автоматически, вы отвлекаете себя, пытаясь принять слишком много ненужных решений, тратя время и когнитивные ресурсы, которые вы можете потратить при решении более сложных задач. - person Domi; 04.10.2019

pthread_cond_wait и pthread_cond_signal могут использоваться для синхронизации на основе условия

person jspcal    schedule 18.01.2010

Одним из способов сделать это является использование переменных условий: доступны при использовании библиотеки pthread под Linux (см. ссылку).

Условная переменная — это переменная типа pthread_cond_t, которая используется с соответствующими функциями для ожидания и последующего продолжения процесса.

person jldupont    schedule 18.01.2010

Если вас не волнует переносимость, 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));.

person Julian Stecklina    schedule 19.06.2013

Если возможно, вы можете использовать семафоры POSIX. В библиотеке pthread есть мьютексы, которые могут подойти и вам.

person user231967    schedule 18.01.2010