Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Многопоточность в C++0x  (Прочитано 5380 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« : 18-12-2013 22:16 » new

Коль уж встал вопрос о кросс-платформенной многопоточности, и коль уж новый стандарт языка её поддерживает, стоит иметь под рукой пример её использования:

Классика жанра: "Обедающие философы"
Код: (C++)
#include <cstdlib>
#include <ctime>
#include <exception>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

using namespace std;

// FORK ////////////////////////////////////////////////////////////////////////

class Fork
{

        friend bool tryToTake(Fork &fork);
        friend void release(Fork &fork);

private:

        timed_mutex _mutex;
        int _number;

public:

        Fork(const int number) :
                _mutex(),
                _number(number)
        {}

        int number() const
        {
                return _number;
        }

};

bool tryToTake(Fork &fork)
{
        return fork._mutex.try_lock_for(chrono::milliseconds(0));
}

void release(Fork &fork)
{
        fork._mutex.unlock();
}

// PHILOSOPHIER ////////////////////////////////////////////////////////////////

class Philosophier
{

private:

        class Implementation
        {

        private:

                enum State
                {
                        Eat,
                        Relax,
                        Wait
                };

                Fork &_leftFork;
                Fork &_rightFork;
                int _number;
                volatile bool _finalize;
                State _state;

                bool _tryToTake(Fork &fork)
                {
                        bool result = tryToTake(fork);
                        if(result)
                        {
                                cout << "Philosophier " << _number << " has taken fork " << fork.number() << "." <<  endl;
                        }
                        else
                        {
                                cout << "Philosophier " << _number << " hasn't taken fork " << fork.number() << "." <<  endl;
                        }
                        return result;
                }

                void _release(Fork &fork)
                {
                        release(fork);
                        cout << "Philosophier " << _number << " has released fork " << fork.number() << "." << endl;
                }

                void _action(const int timeout)
                {
                        switch(_state)
                        {
                        case Relax:
                        case Wait:
                                {
                                        bool leftResult = _tryToTake(_leftFork);
                                        bool rightResult = _tryToTake(_rightFork);
                                        if(leftResult && rightResult)
                                        {
                                                cout << "Philosophier " << _number << " is eating " << timeout << " times." << endl;
                                                _state = Eat;
                                        }
                                        else
                                        {
                                                if(leftResult)
                                                {
                                                        _release(_leftFork);
                                                }
                                                if(rightResult)
                                                {
                                                        _release(_rightFork);
                                                }
                                                cout << "Philosophier " << _number << " is waiting " << timeout << " times." << endl;
                                                _state = Wait;
                                        }
                                }
                                break;
                        case Eat:
                                {
                                        _release(_leftFork);
                                        _release(_rightFork);
                                        cout << "Philosophier " << _number << " is relaxed " << timeout << " times." << endl;
                                        _state = Relax;
                                }
                                break;
                        default:
                                throw exception();
                        }
                }

        public:

                Implementation(const int number, Fork &leftFork, Fork &rightFork) :
                        _number(number),
                        _leftFork(leftFork),
                        _rightFork(rightFork),
                        _finalize(false),
                        _state(Relax)
                {}

                void operator()()
                {
                        cout << "Philosophier " << _number << " began." << endl;
                        while(!_finalize)
                        {
                                int timeout = rand() % 5 + 1;
                                _action(timeout);
                                this_thread::sleep_for(chrono::seconds(timeout));
                        }
                        cout << "Philosophier " << _number << " ended." << endl;
                }

                void finalize()
                {
                        _finalize = true;
                }

        };

        Implementation _implementation;
        thread _thread;

public:

        Philosophier(const int number, Fork &leftFork, Fork &rightFork) :
                _implementation(number, leftFork, rightFork),
                _thread(ref(_implementation))
        {}

        ~Philosophier()
        {
                _implementation.finalize();
                _thread.join();
        }

};

// MAIN ////////////////////////////////////////////////////////////////////////

int main()
{
        srand(time(NULL));
        cout << "Press Enter for exit." << endl;
       
        vector<Fork *> forks;
        vector<Philosophier *> philosophiers;

        const int n = 3;
        Fork *firstFork = new Fork(1), *previousFork = firstFork;
        forks.push_back(previousFork);
        for(int i = 0; i < n; ++i)
        {
                Fork *nextFork;
                if(i < n - 1)
                {
                        nextFork = new Fork(i + 2);
                        forks.push_back(nextFork);
                }
                else
                {
                        nextFork = firstFork;
                }
                Philosophier *philosophier = new Philosophier(i + 1, *previousFork, *nextFork);
                philosophiers.push_back(philosophier);
                previousFork = nextFork;
        }
       

        getchar();

        for(int i = 0; i < philosophiers.size(); ++i)
        {
                delete philosophiers[i];
        }
        for(int i = 0; i < forks.size(); ++i)
        {
                delete forks[i];
        }

        return 0;
}

Вкратце основные положения:

- Запуск потока происходит путём создания объекта класса std::thread (файл <thread>).

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

- (Важно!) Все аргументы thread - как сам функтор, если это объект, так и передаваемые функтору аргументы - передаются по значению, а не по ссылке. Поэтому всегда происходит полное копирование всех переданных аргументов в контекст нового потока. Если в качестве функтора нужно передать конкретный объект, а не его копию, функтор нужно обернуть в объект std::ref() - см. в коде.

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

- Поток можно сделать зомби, вызвав у объекта потока метод detach. А всё потому, что нет сборщика мусора. Поэтому деструктор объекта thread принудительно убивает поток. Поэтому локальная переменная потока, выходящая из области видимости, приводит к срыву работы. Чтобы этого не происходило, нужно делать detach. Извращение. Я бы заставил хранить объект потока до победы, ибо нечего зомби разводить в системе - считай, аналог утечки памяти.

- Глобальная переменная std::this_thread есть объект текущего потока в контексте каждого потока. Кстати говоря, появилось служебное слово thread_local для переменных, которые не локальные, но и не глобальные как static. Т.е. глобальны в контексте потока. Это ещё одно извращение, поскольку всегда можно обеспечить нужный уровень локальности соответствующим объектом.

- У std::this_thread есть метод sleep_for для засыпания потока на таймаут. Вообще, впечатление такое, что в общем случае методы *_for работают с интервалами времени, а методы *_until работают до наступления определённого момента календарного времени. Но в подробности я ещё не погружался.

- Есть некий элемент std::chrono (даже не знаю, класс это или пространство имён), в котором сосредоточены всякие разности для работы со временем. Так chrono::milliseconds и chrono::seconds описывают интервалы времени в соответствующих единицах. Но там есть и точки календарного времени и прочее всякое разное.

- Объекты синхронизации представлены разными мьютексами и условными переменными (файл <mutex>). Но поскольку в подавляющем большинстве случаев достаточно мьютекса (как семафора со счётчиком 1), на нём и остановимся.

- Есть в доску простой std::mutex с методами lock и unlock. Тут и комментировать особо нечего.

- Есть мьютекс с таймаутом std::timed_mutex, у которого в дополнение к простому мьютексу есть методы try_lock_for и try_lock_until с аргументами из chrono с вышеописанной смысловой разницей. Возвращают эти методы bool, означающий успех или неуспех блокировки.

- Есть всякие другие мьютексы, и возможности установки/снятия блокировки по bool-выражению, чтобы исключить гонку. Есть объекты для критических секций. Есть возможность блокировки групп мьютексов. Но до этого руки ещё не дошли.
« Последнее редактирование: 18-12-2013 22:18 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines