Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.116.37.228] |
|
Страницы: (3) 1 [2] 3 все ( Перейти к последнему сообщению ) |
Сообщ.
#16
,
|
|
|
Цитата Wound @ Просто тут фишка в том, что у меня в конструкторе лог открывается, сдается мне, что использование std::thread как члена класса, стартанет поток еще до тела конструктора, что приведет к UB. Он стартанёт, когда ты его стартуешь, m_thread = std::thread(&ThreadProc.....), ни раньше ни позже. |
Сообщ.
#17
,
|
|
|
Ок, вот так получилось, вроде работает. Пожалуй на этом и остановлюсь. Надеюсь тут больше нет косяков?
//Constructor Logger::Logger(const std::wstring& logname, unsigned int cachesize) : m_cachesize(cachesize), m_logname(logname), m_isFinished(false) { openlog(m_logname); m_shadow_flusher = std::thread([this] { while (!m_isFinished) { std::unique_lock<std::mutex> lk(m_flushmtx); m_isflush.wait(lk, [this] {return m_log.size() >= m_cachesize || m_isFinished; }); this->dump(std::move(m_log)); } }); } void Logger::Flush(std::wstring&& msg) { std::unique_lock<std::mutex> lk(m_flushmtx); m_log.push(msg); if (m_log.size() >= m_cachesize) m_isflush.notify_one(); } void Logger::dump(Msi::Queue<std::wstring>&& queue) { try { while (queue.size() > 0u) { std::wstring val; if (queue.try_pop(val)) { m_fout << L"Trhread [" << std::this_thread::get_id() << L"]: " << val; } } m_fout.flush(); } catch (std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; } } Logger::~Logger() { m_isFinished.store(true); m_isflush.notify_one(); m_shadow_flusher.join(); m_fout.close(); } |
Сообщ.
#18
,
|
|
|
Цитата Wound @ m_shadow_flusher = std::thread([this] { while (!m_isFinished) { std::unique_lock<std::mutex> lk(m_flushmtx); m_isflush.wait(lk, [this] {return m_log.size() >= m_cachesize || m_isFinished; }); this->dump(std::move(m_log)); } }); 1. dump() должен вызываться без блокировки. 2. m_cachesize вообще больше не нужен. Событие можно взводить на каждый вызов Flush(), это дешёвая операция m_shadow_flusher = std::thread([this] { while (!m_isFinished) this->dump(WaitMessages()); std::lock_guard lock(m_flushmtx); this->dump(std::move(m_log)); }); inline auto Logger::WaitMessages() { std::unique_lock<std::mutex> lk(m_flushmtx); m_isflush.wait(lk, [this] {return !m_log.empty() || m_isFinished; }); return std::move(m_log); } 3. m_isFinished можно сделать просто volatile bool. В деструкторе его нужно взводить под блокировкой { std::lock_guard lock(m_flushmtx); m_isFinished = true; m_isflush.notify_all(); } m_shadow_flusher.join(); |
Сообщ.
#19
,
|
|
|
Цитата Олег М @ 3. m_isFinished можно сделать просто volatile bool. В деструкторе его нужно взводить под блокировкой Он у меня как std::atomic_bool |
Сообщ.
#20
,
|
|
|
Цитата Wound @ Он у меня как std::atomic_bool Можно без atomic |
Сообщ.
#21
,
|
|
|
Цитата Олег М @ Можно без atomic Так atomic вроде дешевле блокировки. Добавлено И мне не совсем понятно зачем тут два раза dump вызывать? Ведь все равно WaitMessage все сделает. m_shadow_flusher = std::thread([this] { while (!m_isFinished) this->dump(WaitMessages()); std::lock_guard lock(m_flushmtx); this->dump(std::move(m_log)); }); |
Сообщ.
#22
,
|
|
|
Цитата Wound @ Так atomic вроде дешевле блокировки. Начитывается он в любом случае под блокировкой, так что ничего у тебя там не дешевле. И, в общем случае, устанавливать такие переменные надо под блокировкой - там две операции =true b notify_all(), их надо синхронизировать с чтением Добавлено Цитата Wound @ И мне не совсем понятно зачем тут два раза dump вызывать? Ведь все равно WaitMessage все сделает. Да, возможно. На всякиий случай, хуже точно не будет. |
Сообщ.
#23
,
|
|
|
Цитата Олег М @ Начитывается он в любом случае под блокировкой, так что ничего у тебя там не дешевле. И, в общем случае, устанавливать такие переменные надо под блокировкой - там две операции =true b notify_all(), их надо синхронизировать с чтением На сколько я знаю использование std::atomic накладывает ограничение на порядок выполнения операций. Т.е. в данном случае notify_all буквально всегда обязано выполнятся только после присваивания значения atomic переменной. По крайней мере так написано у майерса: Цитата Однако применение s t d : : a t o m i c накладывает ограничения на разрешенные переупорядочения кода, и одно такое ограничение заключается в том, что никакой код, предшествующий в исходном тексте записи переменной s t d : : а t omi с, не может иметь место (или выглядеть таковым для друтих ядер) после нее7. Это означает, что в нашем коде auto imptValue = compute importantVa l ue ( ) ; / / Вычисление значения valAvai l aЫe = true ; 11 Сообщение об этом 11 другому потоку компиляторы должны не только сохранять порядок присваиваний i m p t V a l u e и valAva i laЬle, но и генерировать код, который гарантирует, что так же поведет себя и аппаратное обеспечение. В результате объявление v a lAva i l a Ы e как s t d : : at o m i c гарантирует выполнение критичного требования к упорядоченности - что значение imptVal ue должно быть видимо всеми потоками как измененное не позже, чем значение valAva i l aЬle. Единственное - это файл может закрыться раньше, чем dump вызвался, но у нас ведь гарантированно 1 поток? Добавлено Ладно попробую переписать, посмотрю что получится. |
Сообщ.
#24
,
|
|
|
Цитата Wound @ На сколько я знаю использование std::atomic накладывает ограничение на порядок выполнения операция. Т.е. в данном случае notify_all буквально всегда обязано выполнятся только после присваивания значения atomic переменной. По крайней мере так написано у майерса: notify_all в любом случае вызовется после присвоения значения. Здесь, в принципе, можно и без блокировки. Я говорил про общий случай - лучше выставление значении всегда синхронизировать с проверкой условий в потоке. Проблем будет меньше. Цитата Wound @ Единственное - это файл может закрыться раньше, чем dump вызвался, но у нас ведь гарантированно 1 поток? Не может. Ты join делаешь перед закрытием файла. |
Сообщ.
#25
,
|
|
|
Цитата Олег М @ Я говорил про общий случай - лучше выставление значении всегда синхронизировать с проверкой условий в потоке. Проблем будет меньше. А, ну тогда понял. Цитата Олег М @ Не может. Ты join делаешь перед закрытием файла. Ну да в принципе. Ну вроде все работает с последними изменениями. Спасибо за помощь Олег М. Добавлено На всякий случай выложу что получилось, может быть кому пригодится: #ifndef __LOGGER_H_INCLUDED__ #define __LOGGER_H_INCLUDED__ #include <sstream> #include <mutex> #include <fstream> #include <memory> #include <atomic> #include <future> #include "MsiQueue.h" namespace Msi { class Logger; class UnsafeLogger { public: UnsafeLogger(Logger& logger); UnsafeLogger(UnsafeLogger&& log); ~UnsafeLogger(); public: UnsafeLogger() = delete; UnsafeLogger(const UnsafeLogger& logger) = delete; UnsafeLogger& operator=(UnsafeLogger&&) = delete; UnsafeLogger& operator=(const UnsafeLogger&) = delete; public: template <typename T> UnsafeLogger& operator <<(T &&val); auto& operator <<(std::wostream& (*pfmanip)(std::wostream&)) { m_stream << pfmanip; return *this; } auto& operator <<(UnsafeLogger& (*pfmanip)(UnsafeLogger&)) { return pfmanip(*this); } public: void Flush() noexcept; private: Logger& m_logger; std::wostringstream m_stream; }; class Logger { public: explicit Logger(const std::wstring& logname); ~Logger(); public: Logger(const Logger&) = delete; Logger(Logger&&) = delete; Logger& operator=(const Logger&) = delete; Logger& operator=(Logger&&) = delete; UnsafeLogger Log(); void Flush(std::wstring&& msg); private: void openlog(const std::wstring& logname); void dump(Msi::Queue<std::wstring>&& queue); inline auto wait_messages() { std::unique_lock<std::mutex> lk(m_flushmtx); m_isflush.wait(lk, [this] {return m_log.size() > 0u || m_isFinished.load(); }); return std::move(m_log); } private: std::mutex m_flushmtx; std::wofstream m_fout; std::atomic_bool m_isFinished; Msi::Queue<std::wstring> m_log; std::condition_variable m_isflush; std::thread m_shadow_flusher; }; template<typename T> inline UnsafeLogger& UnsafeLogger::operator<<(T&& val) { m_stream << std::forward<T>(val); return *this; } namespace log { UnsafeLogger& info(UnsafeLogger& stream); UnsafeLogger& error(UnsafeLogger& stream); UnsafeLogger& thread(UnsafeLogger& stream); UnsafeLogger& warning(UnsafeLogger& stream); } } #endif // !__LOGGER_H_INCLUDED__ #include "Logger.h" #include <ctime> #include <thread> #include <future> #include <iostream> namespace Msi { UnsafeLogger::UnsafeLogger(Logger& logger) : m_logger(logger) { } UnsafeLogger::UnsafeLogger(UnsafeLogger&& log) : m_logger(log.m_logger), m_stream(std::move(log.m_stream)) { } UnsafeLogger::~UnsafeLogger() { Flush(); } void UnsafeLogger::Flush() noexcept { m_stream.flush(); m_logger.Flush(std::move(m_stream.str())); } Logger::Logger(const std::wstring& logname) : m_isFinished(false) { openlog(logname); m_shadow_flusher = std::thread([this] { while (!m_isFinished.load()) dump(wait_messages()); std::lock_guard<std::mutex> lk(m_flushmtx); dump(std::move(m_log)); }); } Logger::~Logger() { m_isFinished.store(true); m_isflush.notify_all(); m_shadow_flusher.join(); m_fout.close(); } UnsafeLogger Logger::Log() { return UnsafeLogger(*this); } void Logger::openlog(const std::wstring& logname) { m_fout.open(logname, std::ios_base::out | std::ios_base::trunc); if (!m_fout.is_open()) throw std::runtime_error("Error: can't open log file"); auto now = std::chrono::system_clock::now(); time_t start = std::chrono::system_clock::to_time_t(now); m_fout << L"Log was created at: " << std::ctime(&start); m_fout << L"============================================" << std::endl; } void Logger::Flush(std::wstring&& msg) { { std::unique_lock<std::mutex> lk(m_flushmtx); m_log.push(msg); } m_isflush.notify_all(); } void Logger::dump(Msi::Queue<std::wstring>&& queue) { try { while (queue.size() > 0u) { std::wstring val; if (queue.try_pop(val)) { m_fout << val; } } m_fout.flush(); } catch (std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; } } namespace log { UnsafeLogger& info(UnsafeLogger& stream) { return stream << L"[Information]"; } UnsafeLogger& warning(UnsafeLogger& stream) { return stream << L"[Warning]"; } UnsafeLogger& thread(UnsafeLogger& stream) { return stream << L"Thread [" << std::this_thread::get_id() << L"]"; } UnsafeLogger& error(UnsafeLogger& stream) { return stream << L"[Error]"; } } } Единственное тут есть два момента, во первых вместо моей очереди, нужно подставить любую другую(например нестандартную concurent_queue) или самому написать. Во вторых тут вызывается - std::ctime(&start); функция, которая в Microsoft компиляторах генерирует варнинг, нужно либо убрать вызов этой функции, либо добавить в препроцессор _CRT_SECURE_NO_WARNINGS |
Сообщ.
#26
,
|
|
|
Цитата Wound @ Единственное тут есть два момента, во первых вместо моей очереди, нужно подставить любую другую(например нестандартную concurent_queue) или самому написать Поставь std::list<std::sringstream> |
Сообщ.
#27
,
|
|
|
Цитата Олег М @ Поставь std::list<std::sringstream> А зачем? Мне просто интересно чем std::stringstream лучше? |
Сообщ.
#28
,
|
|
|
Уберёшь из основного потока вызов m_stream.str(), какая-никакая экономия. (Точнее, тебе там надо std::wstringstream)
А с помощью std::list легко можно организовать пул этих стримов. |
Сообщ.
#29
,
|
|
|
Ок.
Вот версия с std::wstringstream: #ifndef __LOGGER_H_INCLUDED__ #define __LOGGER_H_INCLUDED__ #include <sstream> #include <mutex> #include <fstream> #include <memory> #include <atomic> #include <future> #include <list> namespace core { class Logger; class UnsafeLogger { public: UnsafeLogger(Logger& logger); UnsafeLogger(UnsafeLogger&& log); ~UnsafeLogger(); public: UnsafeLogger() = delete; UnsafeLogger(const UnsafeLogger& logger) = delete; UnsafeLogger& operator=(UnsafeLogger&&) = delete; UnsafeLogger& operator=(const UnsafeLogger&) = delete; public: template <typename T> UnsafeLogger& operator <<(T &&val); auto& operator <<(std::wostream& (*pfmanip)(std::wostream&)) { m_stream << pfmanip; return *this; } auto& operator <<(UnsafeLogger& (*pfmanip)(UnsafeLogger&)) { return pfmanip(*this); } public: void Flush() noexcept; private: Logger & m_logger; std::wstringstream m_stream; }; class Logger { public: explicit Logger(const std::wstring& logname); ~Logger(); public: Logger(const Logger&) = delete; Logger(Logger&&) = delete; Logger& operator=(const Logger&) = delete; Logger& operator=(Logger&&) = delete; UnsafeLogger Log(); void Flush(std::wstringstream&& msg); private: void openlog(const std::wstring& logname); void dump(std::list<std::wstringstream>&& queue); inline auto wait_messages() { std::unique_lock<std::mutex> lk(m_flushmtx); m_isflush.wait(lk, [this] {return m_log.size() > 0u || m_isFinished.load(); }); return std::move(m_log); } private: std::mutex m_flushmtx; std::wofstream m_fout; std::atomic_bool m_isFinished; std::list<std::wstringstream> m_log; std::condition_variable m_isflush; std::thread m_shadow_flusher; }; template<typename T> inline UnsafeLogger& UnsafeLogger::operator<<(T&& val) { m_stream << std::forward<T>(val); return *this; } namespace log { UnsafeLogger& info(UnsafeLogger& stream); UnsafeLogger& error(UnsafeLogger& stream); UnsafeLogger& thread(UnsafeLogger& stream); UnsafeLogger& warning(UnsafeLogger& stream); } } #endif // !__LOGGER_H_INCLUDED__ #include "Logger.h" #include <ctime> #include <thread> #include <future> #include <iostream> namespace core { UnsafeLogger::UnsafeLogger(Logger& logger) : m_logger(logger) { } UnsafeLogger::UnsafeLogger(UnsafeLogger&& log) : m_logger(log.m_logger), m_stream(std::move(log.m_stream)) { } UnsafeLogger::~UnsafeLogger() { Flush(); } void UnsafeLogger::Flush() noexcept { m_stream.flush(); m_logger.Flush(std::move(m_stream)); } Logger::Logger(const std::wstring& logname) : m_isFinished(false) { openlog(logname); m_shadow_flusher = std::thread([this] { while (!m_isFinished.load()) dump(wait_messages()); std::lock_guard<std::mutex> lk(m_flushmtx); dump(std::move(m_log)); }); } Logger::~Logger() { m_isFinished.store(true); m_isflush.notify_all(); m_shadow_flusher.join(); m_fout.close(); } UnsafeLogger Logger::Log() { return UnsafeLogger(*this); } void Logger::openlog(const std::wstring& logname) { m_fout.open(logname, std::ios_base::out | std::ios_base::trunc); if (!m_fout.is_open()) throw std::runtime_error("Error: can't open log file"); auto now = std::chrono::system_clock::now(); time_t start = std::chrono::system_clock::to_time_t(now); m_fout << L"Log was created at: " << std::ctime(&start); m_fout << L"============================================" << std::endl; } void Logger::Flush(std::wstringstream&& msg) { { std::unique_lock<std::mutex> lk(m_flushmtx); m_log.push_back(std::move(msg)); } m_isflush.notify_all(); } void Logger::dump(std::list<std::wstringstream>&& queue) { try { while (queue.size() > 0u) { m_fout << queue.front().str(); queue.pop_front(); } m_fout.flush(); } catch (std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; } } namespace log { UnsafeLogger& info(UnsafeLogger& stream) { return stream << L"[Information]"; } UnsafeLogger& warning(UnsafeLogger& stream) { return stream << L"[Warning]"; } UnsafeLogger& thread(UnsafeLogger& stream) { return stream << L"Thread [" << std::this_thread::get_id() << L"]"; } UnsafeLogger& error(UnsafeLogger& stream) { return stream << L"[Error]"; } } } |
Сообщ.
#30
,
|
|
|
Неплохо бы тестовый пример еще.
|