Личный опыт разработки ПО

Сборник рецептов

Многопоточность, баги и списки инициализации

комментариев 19

Разработка многопоточных программ – задача нетривиальная. Локализовать место ошибки сложно, отладка затруднена, иногда даже возникает желание все свалить на ошибки в сторонних библиотеках и/или компилятор/операционную систему. Так делать не надо. Конечно вероятность такая есть, но она несравненно меньше того, что виновником ошибки являетесь именно вы.

Например приложение работает хорошо, ну скажем в 95 случаях из 100, но в оставшихся пяти оно может зависнуть или вылететь с segmentation fault. А может упасть.

Посмотрим на следующий код:

#include <condition_variable>
#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
 
class Worker
{
public:
    Worker()
        : shutdown_(false)
        , thread_(&Worker::taskProcessor, this)
    {
    }
 
    ~Worker()
    {
        {
            std::lock_guard<decltype(mutex_)> lock(mutex_);
            shutdown_ = true;
        }
 
        hasTask_.notify_one();
 
        thread_.join();
    }
 
    void addTask(int taskId)
    {
        {
            std::lock_guard<decltype(mutex_)> lock(mutex_);
            std::cout << "Adding task " << taskId << std::endl;
            tasks_.push(taskId);
        }
 
        hasTask_.notify_one();
    }
 
private:
    void taskProcessor()
    {
        while (true)
        {
            int task = 0;
 
            {
                std::unique_lock<decltype(mutex_)> lock(mutex_);
 
                if (shutdown_)
                    return;
 
                while (tasks_.empty())
                {
                    hasTask_.wait(lock);
 
                    if (shutdown_)
                        return;
                }
 
                task = tasks_.front();
                tasks_.pop();
            }
 
            // освободили мютекс, чтобы не блокировать другие потоки и делаем работу
            std::cout << "Processing task " << task << std::endl;
        }
    }
 
private:
    bool shutdown_;
 
    std::queue<int> tasks_;
 
    std::thread thread_;
    std::mutex mutex_;
    std::condition_variable hasTask_;
};
 
int main(int argc, char* argv[])
{
    Worker worker;
 
    for (int i = 1; i < 6; ++i)
    {
        worker.addTask(i);
    }
 
    return 0;
}

Здесь приведен простой класс, объект которого обрабатывает некоторые задачи в параллельном потоке. Из основного потока функцией addTask можно добавить задачу в очередь и известить об этом параллельный поток. Поток проснувшись, возьмет из очереди задачу, обработает ее и если задач больше нет – уснет. В деструкторе, прежде чем объект будет уничтожен происходит ожидание обработки всех задач в очереди.

На первый взгляд все прозрачно, но в некоторых случаях, мы вылетим по ассерту из стандартной библиотеки (и даже можем начать грешить на его разработчиков) или зависнем, но повторюсь – ошибка здесь наша.

Проблема в списке инициализации. Как мы помним, конструирование членов класса происходит в порядке их объявления. В нашем случае сначала будет создан объект типа std::thread и только после него std::mutex. Если звезды сложатся удачно, то запуск параллельного потока займет некоторое время, за которое в основном потоке будет сконструирован мютекс и программа будет работать. Если со звездами не сложиться, то поток начав работу попробует заблокировать мютекс который еще не сконструирован, со всеми вытекающими. Решение простое, но не самое лучшее, так как делает код хрупким – изменить положение членов в списке инициализации:

boost::mutex Mutex_;
std::condition_variable hasTask_;
std::thread thread_;

Лучше будет разделить конструирование и инициализацию:

class Worker
{
public:
	static std::unique_ptr<Worker> create()
	{
		std::unique_ptr<Worker> worker(new Worker());
		worker->init();
		return worker;
	}
 
	...
 
private:
	Worker()
		: shutdown_(false)
	{
	}
 
	void init()
	{
		thread_ = std::thread(&Worker::taskProcessor, this));
	}
 
	...
 
};

15th Ноябрь 2011
21:38

Рубрика: C++

Метки: , ,

19 комментариев к 'Многопоточность, баги и списки инициализации'

Подписаться на комментарии по RSS или TrackBack.

  1. У тебя тут скорее проблема дизайна, нежели последовательности инициализации переменных. Запуск потока в конструкторе в любом случае чреват кучей проблем, начиная от добавлением элеметов типа Worker в массив, заканчивая неожиданностями типа преждевременного создания объектов, с непонять почему увеличивающимся количеством потоков.

    Александр

    8 марта 12 14:26

  2. Соглашусь с Александром — описанный фикс делает код очень хрупким (легким движением бизнес-требований класс превращается в иерархию с наследником, пришёл джуниор-автоформаттер и наформатировал и т.п.). Лучше разделять конструирование (делаем тред) и инициализацию (запускаем тред) — таких граблей своим лбом собрано немало.

    Yury Schkatula

    17 февраля 14 15:59

  3. term manuscript (late lat.manuscriptum,

    iAquaLinkijh

    27 декабря 20 2:08

  4. from lat. manus — «hand» and scribo — «I write») [1]

    Holographiczku

    28 декабря 20 1:05

  5. Since the era of Charlemagne

    Generationxyv

    5 января 21 15:16

  6. I was examining some of your content on this site and I believe this internet site is real instructive! Retain posting . Tani Dukey Mia

    film izle

    17 января 21 2:51

  7. Since manuscripts are subject to deterioration

    Yamahakvf

    23 января 21 12:55

  8. XVII century was Nicholas Jarry [fr].

    Minelabhpg

    26 января 21 21:39

  9. Libraries of the Carolingian era). IN

    Ascentyso

    29 января 21 11:33

  10. By the end of the 15th century, 35

    Arnottiov

    10 февраля 21 0:03

  11. among them acquired «Moral

    Epiphonebij

    19 февраля 21 7:56

  12. bride, Julie d’Angenne.

    Edelbrocknfj

    3 марта 21 0:11

  13. Western Europe also formed

    Interfaceebe

    7 марта 21 23:07

  14. XVII century was Nicholas Jarry [fr].

    Boschsvc

    31 марта 21 19:11

  15. new texts were rewritten

    Nespressoknp

    11 апреля 21 19:15

  16. book about the chess of love «, created by

    Haywardaeo

    13 апреля 21 2:34

  17. (palimpsests). In the XIII-XV centuries in

    Holographicerq

    13 апреля 21 4:55

  18. Century to a kind of destruction:

    Blendereut

    19 сентября 21 19:03

  19. v9zbwk

Оставить комментарий