Реализация мьютексов для записи файлов

Я пытаюсь использовать взаимные исключения, чтобы избежать нескольких записей в один и тот же поток в C/Cpp. Ниже приведен поток моей программы. Я не понимаю, где включить код блокировки и разблокировки.

main() {
    spawn a worker thread
}
worker_thread() {
    read the input file name 
    read some content
    write the content to the given file name
}

Большая часть реализации, которую я вижу, похоже, имеет что-то вроде этого:

main() {
    pthread_mutex_init(&myMutex;,0);
    *spawn a worker thread*
    pthread_join(thread1, 0);
    pthread_mutex_destroy(&myMutex;);
}
worker_thread() {
    read the input file name 
    read some content
    write the content to the given file name
}

Что я хочу это примерно так:

main() {
    spawn a worker thread
}
worker_thread() {
    read the input file name 
    read some content
    pthread_mutex_init(&myMutex;,0) --> for the given file?
    write the content to the given file name
    pthread_mutex_destroy(&myMutex;);
}

Любые идеи для продолжения высоко ценится. Спасибо!

1 ответ

Решение

Довольно просто создать оболочку для iostream, которая обеспечивает одновременную запись в поток только одного потока. К сожалению, почти как только вы это сделаете, вы столкнетесь с другой проблемой. Это гарантирует, что только один поток может быть вставлен в поток за раз, так что вы получите определенное поведение. Если, однако, у вас есть что-то вроде:

поток 1: sync_stream << a << b << c << '\n';
поток 2: sync_stream << x << y << z << '\n';

То, что вы хотели, было либо:

азбука
хуг

... или еще:

хуг
азбука

Поскольку они находятся в отдельных потоках, порядок их изменения может быть хорошим, но строка вывода из одного потока должна оставаться одной строкой вывода. Что-то вроде:

abxy
CZ

... вероятно, не желательно или не приемлемо. Чтобы убедиться в этом, нам действительно нужны два отдельных класса. Одним из них является синхронизированный поток. Другое - это то, что позволяет нам делать некоторый (более или менее произвольный) набор вставок в поток как единую неделимую "транзакцию". Для этого мы можем использовать пару таких классов:

class transaction {
    std::ostringstream buffer;
public:
    transaction(std::string const &s="") : buffer(s, std::ios::out | std::ios::ate) {}

    template <class T>
    transaction &operator<<(T const &t) {
        buffer << t;
        return *this;
    }

    friend std::ostream &operator<<(std::ostream &os, transaction const &t) {
        return os << t.buffer.str();
    }
};

class sync_stream {
    std::ostream &out;
    std::mutex mutex;
public:
    sync_stream(std::ostream &sink) : out(sink) { }

    void operator<<(transaction const &t) {
        std::lock_guard<std::mutex> l(mutex);
        out << t;
    }    
};

Обратите внимание, что transaction класс поддерживает цепочку, но sync_stream нет (и единственное, что вы можете вставить в него, это transaction). Чтобы использовать их, мы делаем что-то вроде этого:

for (int i=0; i<10; i++)
    threads[i] = std::thread([&]{ 
        for (int i=0; i<10; i++) 
            s << (transaction() << "Thread: " << std::this_thread::get_id() << "\n");
    });

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

Резьба: 140375947724544
Резьба: 140376068564736
Резьба: 140375964509952
Резьба: 140375964509952
Резьба: 140375972902656
Резьба: 140375964509952

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

Резюме

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

Другие вопросы по тегам