Использование boost:: lock_guard для простой блокировки общих данных

Я новичок в библиотеке Boost и пытаюсь реализовать простые потоки производителей и потребителей, которые работают в общей очереди. Мой пример реализации выглядит следующим образом:

#include <iostream>
#include <deque>
#include <boost/thread.hpp>

boost::mutex mutex;
std::deque<std::string> queue;

void producer() 
{
    while (true) {
        boost::lock_guard<boost::mutex> lock(mutex);

        std::cout << "producer() pushing string onto queue" << std::endl;

        queue.push_back(std::string("test"));
    }
}

void consumer()
{
    while (true) {
        boost::lock_guard<boost::mutex> lock(mutex);

        if (!queue.empty()) {
            std::cout << "consumer() popped string " << queue.front() << " from queue" << std::endl;

            queue.pop_front();
        }
    }
}

int main()
{
    boost::thread producer_thread(producer);
    boost::thread consumer_thread(consumer);

    sleep(5);

    producer_thread.detach();
    consumer_thread.detach();

    return 0;
}

Этот код работает так, как я ожидаю, но когда main завершается, я получаю

/usr/include/boost/thread/pthread/mutex.hpp:45:    
    boost::mutex::~mutex(): Assertion `!pthread_mutex_destroy(&m)' failed.
consumer() popped string test from queue
Aborted

(Я не уверен, что результат из consumer имеет значение в этой позиции, но я его оставил.)

Я делаю что-то неправильно в моем использовании Boost?

Ответ 1

Вы передаете свои потоки (производитель и потребитель) объект mutex, а затем отсоединяете их. Они должны бежать вечно. Затем вы выходите из своей программы, а объект mutex больше не действует. Тем не менее, ваши потоки все еще пытаются использовать его, они не знают, что это уже недействительно. Если вы использовали определение NDEBUG, у вас был бы coredump.

Вы пытаетесь написать приложение-демона, и это является причиной отсоединения потоков?

Ответ 2

Немного не по теме, но релевантный imo (... ждет пламени в комментариях).

Потребительская модель здесь очень жадная, циклическая и постоянная проверка данных в очереди. Это будет более эффективно (тратить меньше циклов ЦП), если у вас есть ваши потребительские потоки, которые будут разысканы детерминированно, когда данные будут доступны, используя межпоточную сигнализацию, а не эту петлю блокировки и заглядывания. Подумайте об этом так: в то время как очередь пуста, это по существу жесткий цикл, только сломанный необходимостью получить блокировку. Не идеально?

void consumer()
{
    while (true) {
        boost::lock_guard<boost::mutex> lock(mutex);

        if (!queue.empty()) {
            std::cout << "consumer() popped string " << queue.front() << " from queue" << std::endl;

            queue.pop_front();
        }
    }
}

Я понимаю, что вы учитесь, но я бы не советовал использовать это в "реальном" коде. Для изучения библиотеки, хотя, это прекрасно. К вашему счету, это более сложный пример, чем необходимо для понимания того, как использовать lock_guard, поэтому вы нацелитесь высоко!

В конце концов, вы, скорее всего, сгенерируете (или лучше, если сможете, повторно используете) код для очереди, которая сигнализирует работникам, когда они должны выполнять работу, и затем вы будете использовать lock_guard внутри ваших рабочих потоков для посредничества в доступе к общим данных.

Ответ 3

Когда main завершается, все глобальные объекты уничтожаются. Однако ваши потоки продолжают работать. Поэтому у вас проблемы, потому что потоки обращаются к удаленному объекту.

Нижняя строка - это то, что вы должны прекратить поток перед выходом. Единственное, что нужно сделать, это заставить основную программу ждать (используя boost::thread::join), пока потоки не закончатся. Возможно, вы захотите предоставить какой-либо способ сигнализировать о том, что потоки завершатся, чтобы сэкономить слишком долго.

Другая проблема заключается в том, что ваш потребительский поток продолжает работать, даже если нет данных. Возможно, вам стоит подождать boost::condition_variable, пока не будет указано, что есть новые данные.