Поток безопасности и трубопроводов в потоки - PullRequest
0 голосов
/ 14 мая 2018

Если два разных потока пытаются записать один фрагмент данных каждый в выходной поток, гарантируется ли, что эта операция является поточно-ориентированной для std::string_stream, std::cout, std::err и std::fstream (в C ++ 11 и далее)? (Другими словами, есть ли гарантии, что запись в один и тот же поток из нескольких потоков не нарушит поток или не перемежает отдельные фрагменты данных?)

Под «одним фрагментом данных» я подразумеваю «с одним вызовом оператора канала << с использованием стандартной перегрузки библиотеки». Например, если один поток записывает строку, содержащую миллион 0 с подряд, а другой поток записывает строку, содержащую миллион 1 с подряд, есть ли гарантии, что не будет 0 с, смешанный с 1 с?

Код, проверяющий эту идею: Этот код выводит группу 1 с в одном потоке и группу 0 с в другом потоке. Я не видел чересстрочной развертки при передаче вывода в файл. Я тестировал этот код в Ubuntu 18.04, используя gcc-7.3.0 и компилируя с флагами -std=c++17 -pthread -O3 main.cc -o main, и я не наблюдал никакого смешивания 1 s и 0 s.

#include <iostream>
#include <sstream>
#include <string>
#include <thread>
#include <chrono>

auto now() { 
    return std::chrono::high_resolution_clock::now();
}
typedef decltype(now()) now_time_t;
int main(int argc, char** argv) {
    std::stringstream A {}, B {};
    int count = argc > 1 ? std::stoi(argv[1]) : 1000;
    for(int i = 0; i < count; ++i) {
        A << "0";
        B << "1";
    }
    volatile bool waiting = true;
    now_time_t t1_start_time, t2_start_time, t1_end_time, t2_end_time;
    std::thread t1 = std::thread([&] { 
        while(waiting) ;
        t1_start_time = now();
        std::cout << A.rdbuf(); 
        t1_end_time = now();
    });
    std::thread t2 = std::thread([&] { 
        while(waiting) ;
        t2_start_time = now();
        std::cout << B.rdbuf(); 
        t2_end_time = now();
    });
    waiting = false;
    t1.join();
    t2.join();
    auto t1_total_time = (t1_end_time - t1_start_time).count();
    auto t2_total_time = (t2_end_time - t2_start_time).count();
    std::cerr << "Time difference: " << (t1_start_time - t2_start_time).count() << std::endl;    
    std::cerr << "t1 total time: " << t1_total_time << std::endl;    
    std::cerr << "t2 total time: " << t2_total_time << std::endl;    
}

1 Ответ

0 голосов
/ 14 мая 2018

[iostreams.threadsafety] / 1 Одновременный доступ к объекту потока (30.8, 30.9), объекту буфера потока (30.6) или потоку библиотеки C (30.11) несколькими потоками может привести кв гонке данных (4.7), если не указано иное (30.4).[ Примечание: Гонки данных приводят к неопределенному поведению (4.7). —конечная заметка ]

[iostream.objects.overview] / 5 Параллельный доступ к отформатированному и неформатированному входу синхронизированного (30.5.3.4) стандартного объекта iostream (30.7.4.1) и функции вывода (30.7.5.1) или стандартный поток C несколькими потоками не должны приводить к гонке данных (4.7).[ Примечание: Пользователи по-прежнему должны синхронизировать одновременное использование этих объектов и потоков несколькими потоками, если они хотят избежать чередования символов. —конечная заметка ]

Здесь «стандартный объект iostream» - это один из std::cin, std::cout, std::cerr, std::clog и соответствующих широких потоков (wcin и др.).Такой поток синхронизируется, если только sync_with_stdio(false) не был ранее вызван для него.

Таким образом, одновременные записи в std::cout и др. Не приводят к гонке данных, но могут чередовать символы.Одновременные записи в любой другой поток (например, поток строк, поток файлов) демонстрируют неопределенное поведение из-за гонки данных.

...