Технология поточной защиты. Я что-то упускаю?

Я работаю с несколькими многопоточными кодами для игрового проекта и немного устал от сортировки через рвоту stdout, созданную двумя потоками, используя cout для отладки сообщений одновременно. Я провел некоторое исследование и смотрел на стену часа или два, прежде чем придумать "что-то". Следующий код использует SFML для хранения и потоковой обработки. Муфтеки SFML просто завернуты в критические разделы в окнах.

Заголовок:

#include <sfml\system.hpp>
#include <iostream>
class OutputStreamHack
{
 public:
 OutputStreamHack();
 ~OutputStreamHack();
 ostream& outputHijack(ostream &os);
 private:
 sf::Clock myRunTime;
 sf::Mutex myMutex;
};
static OutputStream OUTHACK;
ostream& operator<<(ostream& os, const OutputStreamHack& inputValue);
</iostream></sfml\system.hpp>

Реализация:

#include <sfml\system.hpp>
#include <iostream>
#include "OutputStreamHack.h"
using namespace std;
OutputStreamHack::OutputStreamHack()
{
 myMutex.Unlock();
 myRunTime.Reset();
}
OutputStreamHack::~OutputStreamHack()
{
 myMutex.Unlock();
 myRunTime.Reset();
}
ostream& OutputStreamHack::outputHijack(ostream &os)
{
 sf::Lock lock(myMutex);
 os<<"<"<<myruntime.getelapsedtime()<<","<<getcurrentthreadid()<<"> "<</myruntime.getelapsedtime()<<","<<getcurrentthreadid()<<"></iostream></sfml\system.hpp>
<p>Использование:</p> <pre class="prettyprint linenums">cout&lt;</pre><code> <p>Хорошо, так что это работает через перегруженный оператор вставки, который накладывает безопасность потока, блокируя итератор в статическом объекте, а затем промывая буфер. Если я правильно понимаю процесс (я в основном программист на самообучении), cout обрабатывает элементы своей цепочки вставки от конца до начала, передавая переменную ostream по цепочке для каждого элемента, который должен быть добавлен к потоку. Как только он достигает элемента OUTHACK, вызывается перегруженный оператор, мьютекс блокируется и поток очищается.</p> <p>Я добавил некоторую информацию об отладке времени/потока для вывода в целях проверки. Пока что мое тестирование показывает, что этот метод работает. У меня есть несколько потоков, избивающих cout с несколькими аргументами, и все выходит в правильном порядке.</p> <p>Из того, что я прочитал, исследуя эту проблему, нехватка безопасности потоков в cout, похоже, является довольно распространенной проблемой, с которой сталкиваются люди, вступая в процесс программирования с резьбой. То, что я пытаюсь понять, - это то, что техника, которую я использую, - простое решение проблемы, или я думаю, что я умный, но что-то не так важно.</p> <p>По моему опыту, слово "умное", когда используется для описания программирования, - это просто кодовое слово для отсроченной боли. Я на что-то здесь, или просто преследую паршивые хаки по кругу?</p> <p>Спасибо!</p>
1 ответ

То, что здесь не является потоковым, не является cout per se. Он вызывает вызовы двух функций в последовательности. std::cout << a << b примерно эквивалентен вызову operator<<(std::cout, a), за которым следует operator<<(std::cout, b). Вызов двух функций в последовательности не гарантирует, что они будут выполняться атомным способом.

Как есть, только выход идентификатора времени и потока защищен мьютексом. Вполне возможно, что между вставкой OUTHACK и val1 будет прокручиваться другой поток, потому что блокировка больше не удерживается после вставки OUTHACK.

У вас может быть operator<< для вашего OutputStreamHack вернуть по значению объект, который разблокируется в деструкторе. Поскольку временные периоды живут до конца каждого полного выражения, код будет удерживать блокировку "до точки с запятой". Однако, поскольку копии могут быть задействованы, это может быть проблематично без конструктора перемещения (или конструктора пользовательской копии в С++ 03, аналогичного auto_ptr gasp).

Другим вариантом является использование существующей безопасности потоков cout (гарантируется языком на С++ 11, но многие реализации были потокобезопасными раньше). Создайте объект, который передает все в член std::stringstream, а затем сразу же записывает все его содержимое при его уничтожении.

class FullExpressionAccumulator {
public:
 explicit FullExpressionAccumulator(std::ostream& os) : os(os) {}
 ~FullExpressionAccumulator() {
 os << ss.rdbuf() << std::flush; // write the whole shebang in one go
 }
 template <typename t="">
 FullExpressionAccumulator& operator<<(T const& t) {
 ss << t; // accumulate into a non-shared stringstream, no threading issues
 return *this;
 }
private:
 std::ostream& os;
 std::stringstream ss;
 // stringstream is not copyable, so copies are already forbidden
};
// using a temporary instead of returning one from a function avoids any issues with copies
FullExpressionAccumulator(std::cout) << val1 << val2 << val3;
</typename>

licensed under cc by-sa 3.0 with attribution.