Реализация мьютексов для записи файлов
Я пытаюсь использовать взаимные исключения, чтобы избежать нескольких записей в один и тот же поток в 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
Конечно, вы получите другие идентификаторы потоков, чем я, и порядок строк, скорее всего, будет отличаться, но каждая строка будет записана как одна целая единица.
Резюме
Рабочие потоки не должны работать напрямую с мьютексом. Это должно быть автоматизировано, чтобы рабочий поток мог сосредоточиться на своей работе и тратить только минимум усилий на основной механизм, необходимый для его работы.