В этой заблокированной области видимости мы получаем доступ к содержимому строкового потока ss, выводим его на экран и освобождаем мьютекс, покидая область видимости. Строка cout.flush()
flush
: cout << ss.rdbuf();
cout.flush();
}
О’кей, это достаточно просто, но утомительно писать, если нужно делать одно и то же раз за разом. Можно сократить создание объекта stringstream таким образом:
stringstream{} << "This is some printed line " << 123 << '\n';
Эта строка создает объект строкового потока, передает ему все, что нужно вывести на экран, а затем снова разрушает его. Жизненный цикл строкового потока сокращается до данной строки. После этого мы не можем выводить на экран данные, поскольку у нас нет доступа к указанному объекту. Какой фрагмент кода последним может получить содержимое потока? Это деструктор stringstream
Мы не можем изменить методы-члены экземпляра stringstream
struct pcout : public stringstream {
~pcout() {
lock_guard
cout << rdbuf();
cout.flush();
}
};
Этот класс
cout
.Кроме того, мы поместили объект cout_mutex
pcout
как статический экземпляр, и теперь все элементы находятся в одном месте. Безопасно откладываем инициализацию с помощью std::call_once
Иногда встречаются специфические разделы кода, которые можно запустить в параллельном контексте в нескольких потоках, при этом перед выполнением самих функций нужно выполнить
Однако данный подход имеет следующие недостатки.
□ Если параллельная функция находится в библиотеке, то пользователь не должен забывать вызывать функцию настройки. Это не упрощает применение библиотеки.
□ Предположим, функция настройки в какой-то степени дорогая, и ее, возможно, даже не требуется выполнять, если параллельные функции, которые ее используют, не запускаются. В таком случае необходим код, определяющий, запускать эту функцию или нет.
В данном примере мы рассмотрим вспомогательную функцию std::call_once
Как это делается
В этом примере мы напишем программу, которая запускает несколько потоков, выполняющих один и тот же код. Несмотря на полностью одинаковый выполняемый ими код, наша функция настройки будет вызвана всего раз.
1. Сначала включим все необходимые заголовочные файлы и объявим об использовании пространства имен std
#include
#include
#include
#include
using namespace std;
2. Далее будем использовать функцию std::call_once
once_flag
для синхронизации между всеми потоками, которые задействуют call_once
для конкретной функции.once_flag callflag;
3. Функция, которая должна быть выполнена всего раз, выглядит так. Она просто выводит один восклицательный знак:
static void once_print()
{
cout << '!';
}
4. Все потоки будут выполнять функцию print
once_print
для функции std::call_once
. Функции call_once
требуется переменная callflag
, которую мы определили ранее. Она послужит для управления потоками.static void print(size_t x)
{
std::call_once(callflag, once_print);
cout << x;
}
5. О’кей, теперь запустим десять потоков, все они будут использовать функцию print
int main()
{
vector
for (size_t i {0}; i < 10; ++i) {
v.emplace_back(print, i);
}
for (auto &t : v) { t.join(); }
cout << '\n';
}