LINUX.ORG.RU

Года бегут, а все-равно ваш C++ - ...

 ,


1

9

В соседнем треде промелькнула очень интересная мысль.

Отличие синьора-помидора на C++ от мидла в том, что последний уже знает что C++ говно, но еще не знает, почему.

Внимание, вопрос уровня синьора-помидора. Дан код. Объясните, почему другой синьор-помидор обосрался, написав его? Где может обосраться пользователь?

Задачу не будем усложнять, допустим, у нас single producer - single consumer предполагается.

#ifndef __BLOCKING_QUEUE_HPP__
#define __BLOCKING_QUEUE_HPP__

#include <cstdlib>
#include <mutex>
#include <condition_variable>

template<typename T>
class BlockingQueue
{
private:

    struct QueueNode
    {
        T val;
        QueueNode * next;
    };

    QueueNode *_first, *_last;
    std::mutex _cs;
    std::condition_variable _cv;
    bool _abort;
    int _count;

public:

    BlockingQueue()
    {
        _first = _last = nullptr;
        _abort = false;
        _count = 0;
    }

    ~BlockingQueue()
    {
        Flush();
    }
    
    BlockingQueue(const BlockingQueue& rhs) = delete;

    void operator=(const BlockingQueue& rhs) = delete;

    bool Put(const T& val)
    {
        std::unique_lock<std::mutex> lock(_cs);

        if(!_abort)
        {
            QueueNode * node = (QueueNode*)malloc(sizeof(QueueNode));
            if (node)
            {
                new (&node->val) T(val);
                node->next = nullptr;
                if (_last)
                    _last->next = node;
                else
                    _first = node;
                _last = node;
                ++_count;
                _cv.notify_one();
                return true;
            }
        }
        return false;
    }

    bool Get(T& val)
    {
        std::unique_lock<std::mutex> lock(_cs);

        for (;;)
        {
            if (_abort) return false;

            QueueNode * node = _first;
            if (node)
            {
                _first = node->next;
                if (!_first) _last = nullptr;
                --_count;
                val = node->val;
                node->val.~T();
                free(node);
                return true;
            }
            else
            {
                _cv.wait(lock);
            }
        }
    }

    int Count()
    {
        return _count;
    }

    void Flush()
    {
        QueueNode *node, *tmp;

        std::unique_lock<std::mutex> lock(_cs);

        for (node = _first; node; node = tmp)
        {
            tmp = node->next;
            node->val.~T();
            free(node);
        }

        _first = nullptr;
        _last = nullptr;
        _count = 0;
    }

    void Abort()
    {
        std::unique_lock<std::mutex> lock(_cs);
        _abort = true;
        _cv.notify_one();
    }

    void Start()
    {
        std::unique_lock<std::mutex> lock(_cs);
        _abort = false;
        _cv.notify_one();
    }
};

#endif // __BLOCKING_QUEUE_HPP__

Вопрос номер два - назовите хотя бы один язык программирования с подобными проблемами.

★★★
Ответ на: комментарий от utf8nowhere

Внезапно, из стандарта.

Ты еще скажи, что компилятор g++, например, как-то по особому работает с std::mutex и если его скопировать и переименовать, или взять бустовскую реализацию, - магия пропадет.

Как соответствовать стандарту, не имея аннотаций, сам догадаешься или подсказать?

Да, не выдумывать глупностей.

anonymous
()
Ответ на: комментарий от anonymous

Ты еще скажи, что компилятор g++, например, как-то по особому работает с std::mutex и если его скопировать и переименовать, или взять бустовскую реализацию, - магия пропадет.

Там внутри используется __gthread_recursive_mutex_lock(). Так что да, если сделать аналогичный класс, но вызов __gthread_recursive_mutex_lock() заменить на вызов, скажем, на clock_gettime(), то магия пропадёт.

Surprise!!

i-rinat ★★★★★
()
Последнее исправление: i-rinat (всего исправлений: 1)
Ответ на: комментарий от i-rinat

Там внутри используется __gthread_recursive_mutex_lock(). Так что да, если сделать аналогичный класс, но вызов __gthread_recursive_mutex_lock() заменить на вызов, скажем, на clock_gettime(), то магия пропадёт.
Surprise!!

Во-первых для std::mutex используется явно не __gthread_recursive_mutex_lock. Во-вторых, ты либо не умеешь читать, либо очень жестко тупишь.

anonymous
()
Ответ на: комментарий от anonymous

Во-первых для std::mutex используется явно не __gthread_recursive_mutex_lock.

А, ну да. Не на тот класс смотрел. В mutex используется __gthread_mutex_lock.

Во-вторых, ты либо не умеешь читать

Ага. Не вникал. Какой тезис-то? У вас тут куча мелких сообщений (на протяжении нескольких месяцев). Вы точно оба в курсе, о чём спорите вообще? Я вот просто до одного сообщения докопался. Думал, у вас тут так принято.

i-rinat ★★★★★
()
Ответ на: комментарий от anonymous

Вижу, догадаться ты не смог. Объясняю:

1. Компилятор может заглянуть внутрь метода и увидеть там built-in функцию с известной ему семантикой.

2. Если он не может заглянуть внутрь метода или функции, т.к. она, например, находится в другой единице трансляции, то компилятор вынужден делать самые пессимистичные предположения о том, что функция делает.

utf8nowhere ★★★
()
Последнее исправление: utf8nowhere (всего исправлений: 1)
Ответ на: комментарий от utf8nowhere

Компилятор может заглянуть внутрь метода и увидеть там built-in функцию с известной ему семантикой.

Во-первых это не built-in функции и должны ими быть. То, что привели выше, - это или макрос или inline-функция, а дергаться будет банальный pthread_mutex_lock.

Если он не может заглянуть внутрь метода или функции, т.к. она, например, находится в другой единице трансляции, то компилятор вынужден делать самые пессимистичные предположения о том, что функция делает.

Замечательно, а теперь скажи, нахера компилятору знать о «функциях с известной ему семантикой» в таком случае.

anonymous
()
Ответ на: комментарий от anonymous

и не должны ими быть

починено

anonymous
()
Ответ на: комментарий от anonymous

Компилятор знает о том, что лок мьютекса это acquire operation

Откуда? В общем случае это тупо вызов библиотечной функции.

«Тупо» — это не про вызов библиотечной функции, а про твои сообщения. А про вызов библиотечной функции стандарт говорит:
Certain library calls synchronize with other library calls performed by another thread.
(http://eel.is/c draft/intro.races#6)

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

А про вызов библиотечной функции стандарт говорит

Более глупого вырывания слов из контекста я еще не видел. Там вообще не о том разговор.

anonymous
()
Ответ на: комментарий от utf8nowhere

И чтоб упырить свой мел, может взять исходники g++ (не libstdc++ и libc++) и погрепать на предмет std::mutex и пр.

anonymous
()
Ответ на: комментарий от anonymous

Замечательно, а теперь скажи, нахера компилятору знать о «функциях с известной ему семантикой» в таком случае.

Чтобы генерировать более оптимальный код.

utf8nowhere ★★★
()
Ответ на: комментарий от anonymous

И чтоб упырить свой мел, может взять исходники g++ (не libstdc++ и libc++) и погрепать на предмет std::mutex и пр.

Каким образом это упырит мой мел? Отсутствие там упоминаний std::mutex докажет, что компилятор может вынести `if (_abort) return false;` за пределы цикла?

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

Чтобы генерировать более оптимальный код.

Не имеет на это право т.к. не встроенные функции могут иметь произвольную реализацию, и даже вообще выполнять другие задачи.

anonymous
()
Ответ на: комментарий от anonymous

Не имеет на это право т.к. не встроенные функции могут иметь произвольную реализацию

Ты, я смотрю, совсем тормоз и не помнишь, о чём шла речь два сообщения назад. Я написал:

Компилятор может заглянуть внутрь метода и увидеть там built-in функцию с известной ему семантикой.

Так что под «функциями с известной компилятору семантикой» только встроенные и подразумевались. Нафига ты тут капитанишь про «не встроенные функции могут иметь произвольную реализацию»?

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

Ты, я смотрю, совсем тормоз и не помнишь, о чём шла речь два сообщения назад. Я написал:
Компилятор может заглянуть внутрь метода и увидеть там built-in функцию с известной ему семантикой.

Это ты тормоз, так как тебе, тормозу уже рассказали, что в данном случае никакой built-in функции там нет.

Так что под «функциями с известной компилятору семантикой» только встроенные и подразумевались. Нафига ты тут капитанишь про «не встроенные функции могут иметь произвольную реализацию»?

Замечательно, возвращаемся к «компилятор знает о том, что лок мьютекса это acquire operation». Откуда?

anonymous
()
Ответ на: комментарий от anonymous

Это ты тормоз, так как тебе, тормозу уже рассказали, что в данном случае никакой built-in функции там нет.

Ты спрашивал в общем, как компилятор может/должен узнать о том, что лок мьютекса обладает определёной семантикой. Я тебе ответил — из стандарта. Ты спросил, как этого добиться на практике. Я тебе тоже ответил, что есть два варианта: либо там built-in функция, либо обычная. И описал, как компилятор может поступить в этих случаях, чтобы добиться эффекта, требуемого стандартом.

Ты взял конкретную реализацию стандартной библиотеки, увидел там не built-in функцию. Значит компилятор будет руководствоваться пессимистичным сценарием.

Что касается вопроса «зачем нужны встроенные функции с известной семантикой, если всегда есть пессимистичный сценарий для не-встрокенных», отвечаю ещё раз: для эффективности.

Что-то тебе ещё непонятно?

Замечательно, возвращаемся к «компилятор знает о том, что лок мьютекса это acquire operation». Откуда?

Замечательно, возвращаемся к ответу «из стандарта».

utf8nowhere ★★★
()
Последнее исправление: utf8nowhere (всего исправлений: 1)

Ну и если продолжить эту тему, clang предлагает использовать атрибуты:

https://clang.llvm.org/docs/ThreadSafetyAnalysis.html

Вряд-ли это сколько-нибудь влияет на генерацию кода, но зато позволяет находить некоторые проблемы на этапе компиляции.

anonymous
()
Ответ на: комментарий от utf8nowhere

Что касается вопроса «зачем нужны встроенные функции с известной семантикой, если всегда есть пессимистичный сценарий для не-встрокенных», отвечаю ещё раз: для эффективности.

Назови хоть один компилятор, где есть встроенные функции для std::mutex. Ты себе что-то выдумал, но этого просто нет. Ни в стандарте, ни в реализациях.

Замечательно, возвращаемся к ответу «из стандарта».

Замечательно, а где в коде компилятора присутствует это знание? Мне подойдет любой из.

anonymous
()
Ответ на: комментарий от anonymous

Назови хоть один компилятор, где есть встроенные функции для std::mutex.

Я писал, что там может быть built-in функция. Если такой реализации не существует — не значит, что так сделать нельзя.

Замечательно, а где в коде компилятора присутствует это знание?

Раскрой вопрос.

utf8nowhere ★★★
()

Ха! Ещё не зная крестов, я уже знал, что они - оно самое =)
Достаточно было посмотреть код.

Deleted
()
Ответ на: комментарий от hobbit

Каким боком язык с undefined behaviour на каждый чих с интегральными типами неопределённой длины и бинарного представления становится «переносимым», не говоря уже об «ассемблере»?

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

Часто ты учитываешь что int может быть 16 бит, и думаешь о том, что переполнение int - undefined behaviour?

Ну а то, что на нём можно писать программки и компилировать под разные процессорные архитектуры, в отличие от конкретного языка ассемблера, и оно даже кое-как будет работать - это скорее вопреки языку C как таковому. Но по сравнению с языками ассемблера это был большой шаг вперёд. В начале 70х.

А вообще - скомпилируй программу на системе с базовой кодировкой UTF16-LE, а потом запусти её на системе с UTF-8. И потом расскажешь о переносимости программ на великом и могучем C. Если что, то локаль можно поменять в рантайме на почти любой системе. Это не константное свойство операционной системы иметь ту или иную базовую кодировку.

dzidzitop ★★
()
Последнее исправление: dzidzitop (всего исправлений: 4)
Ответ на: комментарий от utf8nowhere

Видимости последующим чтением (subsequent read).

Читай, одним словом, плюсовый стандарт по многопоточности. Там всё написано.

dzidzitop ★★
()
Ответ на: комментарий от dzidzitop

А можешь, если есть возможность, показать как должен выглядеть код с мьютексом и в паре слов описать кто его увидит а кто нет?

Я ни разу не сиплюс плюсник (наверное месяцев 6 реального опыта лет 8 назад). Проблему с тем что необходимо объявить конструктор копирования/перемещения я увидел (если я верно понял), а тут вообще не пойму о чём речь, но любопытно. Если не пожалеешь 10 минут, буду благодарен.

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 1)
Ответ на: комментарий от dzidzitop

Я понимаю, что ты имел в виду под видимостью.

Расскажи, как выглядит сценарий использования класса, при котором в конструктор (!!!) нужно совать мьютексы. Кому ты собираешься обеспечивать видимость таким образом, где будут эти последующие чтения.

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

Встречный вопрос: в чём разница между обычной член-функцией и конструктором? Ответ: никакой разницы нет. Не нужно приписывать конструктору магических свойств или специфичного использования.

Сценарий простой: создать объект и передать его в другой поток, например, для фоновой обработки. При этом из потока-создателя идёт мониторинг состояния объекта.

Без обеспечения видимости будет undefined behaviou, хотя бы (но не только) из-за того, что указатель на объект может быть передан до того, как конструктор проинициализирует поля.

И сценариев множество.

dzidzitop ★★
()
Ответ на: комментарий от AndreyKl

Из того, что под руками:

https://github.com/dzidzitop/gravifon_lastfm_scrobbler_deadbeef_plugins/blob/...

Тут используется RAII для срабатывания release. А объект, который создаётся - статический, и порядок инициализации единиц трансляции плеера и плагина (а, соответственно, контроль за созданием потоков) мне неизвестен, т.е. не гарантирован. Поэтому тут лок необходим.

dzidzitop ★★
()
Ответ на: комментарий от dzidzitop

Встречный вопрос: в чём разница между обычной член-функцией и конструктором? Ответ: никакой разницы нет.

Т.е. ожидается, что обычные члены-функции будут вызываться до окончания работы конструктора?

Сценарий простой: создать объект и передать его в другой поток

Здесь подробнее. Каким способом нужно передавать объект в другой поток, чтобы была необходимость во мьютексе в конструкторе?

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

Про способ - см пример выше. Есть плеер deadbeef, который как-то создаёт свои потоки, есть мой плагин, который инициализируется самостоятельно. Потоки плеера используют данные из моего плагина. Поскольку процесс я не контролирую, то лок в конструкторе будет гарантировать в моём случае, что после захвата того же лока в потоке плеера я увижу корректно проинициализированный объект.

Не ожидается, что будут вызваны. Посмотри проблему double-checked locking и зачем там нужны барьеры памяти. Это другой, уже классический, пример того, зачем нужен release после выполнения инициализации конструктором.

dzidzitop ★★
()
Последнее исправление: dzidzitop (всего исправлений: 1)
Ответ на: комментарий от dzidzitop

Поскольку процесс я не контролирую, то лок в конструкторе будет гарантировать в моём случае, что после захвата того же лока в потоке плеера я увижу корректно проинициализированный объект.

Т.е. неизвестно, отработал ли конструктор, но методы вызываются?

Посмотри проблему double-checked locking и зачем там нужны барьеры памяти. Это другой, уже классический, пример того, зачем нужен release после выполнения инициализации конструктором.

Ага. И в double-checked locking release находится вне конструктора. ЛОЛ.

Никому в здравом уме не придёт в голову делать release из конструктора.

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

В общем, не буду тратить жизненную энергию на объяснение. Стандарт и отслеживание цепочек acquire-release помогут всё понять.

Одно только, про double-checked locking. Там барьер памяти ставится после выполнения конструктором инициализации. А какая разница для процессора, стоит он в исходниках просле инициализации в конце конструктора или сразу же после выхода из конструктора? А то, что не всегда возможно/уместно ставить барьер памяти после выхода из конструктора и то, что если класс позиционируется как thread-safe, то он не должен иметь undefined behaviour при использовании в многопоточной среде без синхронизации, должно навести на мысли. Если не наводит - то и ладно.

dzidzitop ★★
()
Последнее исправление: dzidzitop (всего исправлений: 1)
Ответ на: комментарий от dzidzitop

В общем, не буду тратить жизненную энергию на объяснение.

Ты хотел сказать, что ты не смог оправдать своей кривой архитектуры. И не видишь перспектив. Это правильно. Кривую архитектуру никак не оправдать.

Если есть плагин и заинтересованные в нём пользователи, то система загрузки плагинов должна обеспечивать сигнализацию о том, что плагин загружен и готов к работе. Через эту сигнализацию и происходит синхронизация между кодом, инициализирующим плагин и использующим его.

Стандарт и отслеживание цепочек acquire-release помогут всё понять.

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

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

Я там дописал в пред. комментарии. Считаешь бредом - неси свою правду в массы.

А архитектуры, где нужно так «нелепо» использовать примитивы синхронизации - любая, где есть совместимый со стандартом C++11 (и позже) компилятор языка C++.

dzidzitop ★★
()
Ответ на: комментарий от utf8nowhere

Ты хотел сказать, что ты не смог оправдать своей кривой архитектуры.

Я хотел сказать, что не очень собираюсь тратить своё время на то, чтобы текстом объяснять что-то человеку, который убеждён в обратном, и при этом считает меня идиотом.

Был бы коллегой у доски с мелом, тогда поговорили бы. А так - кури стандарт.

dzidzitop ★★
()
Последнее исправление: dzidzitop (всего исправлений: 1)
Ответ на: комментарий от dzidzitop

А то, что не всегда возможно/уместно ставить барьер памяти после выхода из конструктора

Лок в конструкторе был бы нужен, если бы был риск, что какой-то метод будет вызван во время работы конструктора. Но вызывать метод у объекта, для которого не завершена работа конструктора — это UB. Поэтому локи в конструкторе бессмысленны. Даже для синхронизации между тредом, создающим объект и тредом, использующим его.

Может ты наконец расскажешь, что это за замечательная архитектура, где одновременно:
1. Нет возможности поставить барьер вне конструктора.
2. Не может возникнуть UB из-за вызова метода у не до конца сконструированного объекта.

если класс позиционируется как thread-safe, то он не должен иметь undefined behaviour при использовании в многопоточной среде без синхронизации, должно навести на мысли. Если не наводит - то и ладно.

Посмотрел в Anthony Williams «C++ Concurrency in Action», в главу «Designing lock-based concurrent data structures», локов в конструкторах у thread-safe стека и очереди не заметил. Если тебя это не наводит ни на какие мысли — то ладно.

utf8nowhere ★★★
()
Ответ на: комментарий от dzidzitop

А архитектуры, где нужно так «нелепо» использовать примитивы синхронизации - любая, где есть совместимый со стандартом C++11 (и позже) компилятор языка C++.

Ну зачем ты врёшь, а?

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

Хорош нести бред и ссылаться на структуры данных рабиновичей по телефону.

Покажи пальчиком на уровне положений модели памяти C++11 (synchronised-with, release/acquire, etc) в моём нелепом коде почему лок в конструкторе там лишний - т.е. почему видимость инициализации конструктором там гарантируется - и запостай багом.

Задолбали уже люди, которые не умеют формально рассуждать о выполнении многопоточного кода.

Мне пофиг ставишь ты в конструкторы локи или нет и копипастишь код из книжек или нет. Хватает таких писателей кода и без тебя. А я уж как-нибудь со своей космической глупостью и моделью памяти C++ буду жить.

dzidzitop ★★
()
Ответ на: комментарий от dzidzitop

А какая разница для процессора

Абсолютно похеру на процессор. Стандарт говорит, что вызывать методы у не сконструированного объекта это UB. А в твоей замечательной архитектуре с захватами мьютекса внутри конструктора и методов получается, что инициализация объекта становится видна только после вызова метода этого объекта.

utf8nowhere ★★★
()
Ответ на: комментарий от dzidzitop

Покажи пальчиком на уровне положений модели памяти C++11 (synchronised-with, release/acquire, etc) в моём нелепом коде почему лок в конструкторе там лишний - т.е. почему видимость инициализации конструктором там гарантируется

Видимость инициализации должна быть гарантирована до вызова любого метода. См. мой ответ выше.

А что у тебя гарантируется — так это UB.

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

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

Да что я такое пишу? Раз инициализация не была ещё видна на момент вызова метода, то и лок мьютекса дальше ведёт нас к UB, т.к. этот мьютекс — мембер объекта, инициализацию которого мы ещё не увидели. Значит, этот мьютекс тоже ещё не инициализирован.

Впрочем, есть ли смысл рассуждать о дальнейшем UB от лока неинициализированного мьютекса, когда у нас уже UB из-за вызова метода у содержащего мьютекс неинициализированного объекта? Нет смысла.

dzidzitop может хоть это наведёт тебя на мысль, почему везде используется внешняя синхронизация. Хотя, вряд ли тебе уже можно помочь... Я пишу это только с целью показать остальным, почему так — синхронизировать поток, создающий объект, с потоками, использующими этот объект с использованием лежащего в этом же объекте мьютекса — делать ни в коем случае не надо.

utf8nowhere ★★★
()
Ответ на: комментарий от dzidzitop

Но по сравнению с языками ассемблера это был большой шаг вперёд. В начале 70х.

Совсем поехавший? В 70 году уже 10 лет исполнилось алголу, 6 лет как существовал PL/1, на котором писали multics, не говоря уж о всяких Коболах и Симулах.

В те времена была некоторая эйфория от достижений в области языков высокого уровня, казалось ещё чуть-чуть и можно будет писать программы на естественном языке. В связи с чем языки старались создавать так, чтобы они приближались к естественным. С другой стороны не было понимания, что стоит пихать в язык, а что оставить в виде библиотек. Опять же успехи по внедрению высокоуровневых абстракций провоцировали запиливать всё новые фичи на каждый чих.

Си был прорывом в том смысле, что он сломал эти шаблоны предложив другую концепцию - высокоуровневого языка для машины, а не для человека. При этом из сферы языка выкидывались сложные абстракции - даже строки по сути не являются частью языка, как например в алголе/паскеле. Вместо этого предлагалось пилить алгоритмы непосредственно. Т.о. в Си произошёл переход от программирования на абстракциях языка, к программированию на абстракциях машины и использованию паттернов. Например строка в Си - это паттерн «массив символов», а не полноценная языковая абстракция. Тут можно проследить определённые параллели между CISC и RISC - Си был таким же прорывом среди языков, каким был RISC для процессоров.

А потом появился C++ и все полимеры успешно просрали.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)

~BlockingQueue()

Что означает тильда перед классом? Я все никак узнать не могу как называется

Deleted
()
Ответ на: комментарий от dzidzitop

Покажи пальчиком на уровне положений модели памяти C++11 (synchronised-with, release/acquire, etc) в моём нелепом коде почему лок в конструкторе там лишний

Он не то, чтобы лишний, он абсолютно бессмысленный. Смотрим в стандарт:

http://eel.is/c++draft/thread.mutex#requirements.mutex-5.note-2

[ Note: Construction and destruction of an object of a mutex type need not be thread-safe; other synchronization should be used to ensure that mutex objects are initialized and visible to other threads. — end note ]

Так как ты неинициализированным мьютексом собрался устанавливать видимость инициализации?

utf8nowhere ★★★
()
Ответ на: комментарий от dzidzitop

Благодарю.

Посидел несколько часов, повспоминал с++. Если я верно понял мысль, то вот этот код должен показать что мьютекс спасает от неопределённого поведения. Версию с m_mutex я запусить не смог, она сегфолтится, поэтому приходится передавать мьютекс через параметры в конструктор и в метод. Но поведение текущей версии должно быть аналогично и лучше потому что мьютекс точно инициализирован по-людски до того как его используют.


#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
#include <stdexcept>

using namespace std;

void doLongThing() {
  cout << "starting long initialization of class MyTestTread" << endl;
  this_thread::sleep_for(chrono::seconds(2));
  cout << "long initialization of class MyTestTread has finished" << endl;
}

class MyTestTread {
public:
  MyTestTread(const bool useMutex, mutex &_mutex) { // : m_mutex() {
    cout << "flag: " << flag << endl;
    if(useMutex) {
      lock_guard<mutex> lock(_mutex); // synchronising memory
      cout << "using mutex" << endl;
      doLongThing();
      flag = 2;
    } else {
      cout << "do not use mutex" << endl;
      doLongThing();
      flag = 2;
    }
    
    cout << "flag: " << flag << endl;
  }
  
  ~MyTestTread(){}

  void checkProblemAndPrintMessage(mutex &_mutex) {
    unique_lock<mutex> lock(_mutex);
    cout << "flag: " << flag << endl;
    if(flag == 2) {
      cout << "\t---== No probs man, it's all right! Congratulations! ===---" << endl;
    } else {
      cout << "\n\t---=== Poor poor man, something definetely went wrong... how flag could be " << flag << " in this moment? Only the way is we have undefined behaviour here. I think. ===---" << endl << endl;
    }
  }

private:
  // mutex m_mutex;
  int flag = 0;
};

mutex m;

void createAndInit(MyTestTread *t, const bool useMutex) {
  // this_thread::sleep_for(chrono::seconds(1));
  t = new MyTestTread(useMutex, m);
}

void callCheckProblem(MyTestTread *t) {
  // just make sure order is always the same... 
  // if delete this line, results are about the same, but order of calling constructor and checkProblemAndPrintMessage can be different
  this_thread::sleep_for(chrono::seconds(1));
  t->checkProblemAndPrintMessage(m);
}

int main(int argc, char* argv[]) {
  string argv1(argv[1]);
  if(argc != 2 || (argv1 != "mutex" && argv1 != "nomutex")) {
    string p1("usage:\n\t- either\t");
    throw invalid_argument(p1 + argv[0] + " mutex\n\t- or\t\t" + argv[0] + " nomutex");
  }
  
  bool useMutex(argv1 == "mutex");
  MyTestTread *t;
  
  thread t_createAndInit(createAndInit, t, useMutex),
         t_callCheckProblem(callCheckProblem, t);

  t_callCheckProblem.join();
  t_createAndInit.join();
}


компилять:
g++ -std=c++11 test.cpp -lpthread


результаты без мьютекса:
./a.out nomutex
flag: 0
do not use mutex
starting long initialization of class MyTestTread
flag: 4200144

        ---=== Poor poor man, something definetely went wrong... how flag could be 4200144 in this moment? Only the way is we have undefined behaviour here. I think. ===---

long initialization of class MyTestTread has finished
flag: 2


результаты с мьютексом:
 ./a.out mutex
flag: 0
using mutex
starting long initialization of class MyTestTread
long initialization of class MyTestTread has finished
flag: 2
flag: 4200144

        ---=== Poor poor man, something definetely went wrong... how flag could be 4200144 in this moment? Only the way is we have undefined behaviour here. I think. ===---


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

AndreyKl ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.