Не виждам как това ще стане лесно с помощта на наследеното C vprintf
с va_list
s. Тъй като искате да предавате неща между нишките, рано или късно ще трябва да използвате купчината по някакъв начин.
По-долу е дадено решение, което използва Boost.Format за форматирането и Boost.Variant за предаване на параметри. Примерът е завършен и работи, ако свържете следните кодови блокове по ред. Ако компилирате с GCC, трябва да подадете -pthread
флага за свързване. И разбира се, ще ви трябват и двете библиотеки Boost, които обаче са само заглавки. Ето заглавките, които ще използваме.
#include <condition_variable>
#include <iostream>
#include <list>
#include <locale>
#include <mutex>
#include <random>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#include <boost/format.hpp>
#include <boost/variant.hpp>
Първо, имаме нужда от някакъв механизъм за асинхронно изпълнение на някои задачи, в този случай отпечатване на нашите съобщения за регистриране. Тъй като концепцията е обща, използвам „абстрактен“ базов клас Spooler
за това. Кодът му се основава на беседата на Хърб Сътър „Програмиране без заключване (или жонглиране с бръсначи)“ на CppCon 2014 (част 1, част 2). Няма да навлизам в подробности за този код, защото той е предимно скеле, което не е пряко свързано с вашия въпрос и предполагам, че вече имате тази част от функционалността. Моят Spooler
използва std::list
защитен от std::mutex
като опашка със задачи. Може да си струва вместо това да обмислите използването на структура от данни без заключване.
class Spooler
{
private:
bool done_ {};
std::list<std::function<void(void)>> queue_ {};
std::mutex mutex_ {};
std::condition_variable condvar_ {};
std::thread worker_ {};
public:
Spooler() : worker_ {[this](){ work(); }}
{
}
~Spooler()
{
auto poison = [this](){ done_ = true; };
this->submit(std::move(poison));
if (this->worker_.joinable())
this->worker_.join();
}
protected:
void
submit(std::function<void(void)> task)
{
// This is basically a push_back but avoids potentially blocking
// calls while in the critical section.
decltype(this->queue_) tmp {std::move(task)};
{
std::unique_lock<std::mutex> lck {this->mutex_};
this->queue_.splice(this->queue_.cend(), tmp);
}
this->condvar_.notify_all();
}
private:
void
work()
{
do
{
std::unique_lock<std::mutex> lck {this->mutex_};
while (this->queue_.empty())
this->condvar_.wait(lck);
const auto task = std::move(this->queue_.front());
this->queue_.pop_front();
lck.unlock();
task();
}
while (!this->done_);
}
};
От Spooler
сега извличаме Logger
, който (частно) наследява своите асинхронни възможности от Spooler
и добавя специфичната функционалност за регистриране. Той има само един функционален член, наречен log
, който приема като параметри низ за форматиране и нула или повече аргументи за форматиране в него като std::vector
от boost::variant
s.
За съжаление това ни ограничава до фиксиран брой типове, които можем да поддържаме, но това не би трябвало да е голям проблем, тъй като C printf
също не поддържа произволни типове. Заради този пример използвам само int
и double
, но можете да разширите списъка с std::string
s, void *
указатели или каквото искате.
Функцията log
конструира ламбда израз, който създава boost::format
обект, подава му всички аргументи и след това го записва в std::log
или където искате да отиде форматираното съобщение.
Конструкторът на boost::format
има претоварване, което приема форматиращия низ и локал. Може да се интересувате от това, тъй като споменахте задаване на персонализиран локал в коментарите. Обичайният конструктор приема само един аргумент, форматиращия низ.
Обърнете внимание как цялото форматиране и извеждане се извършва в нишката на спулера.
class Logger : Spooler
{
public:
void
log(const std::string& fmt,
const std::vector<boost::variant<int, double>>& args)
{
auto task = [fmt, args](){
boost::format msg {fmt, std::locale {"C"}}; // your locale here
for (const auto& arg : args)
msg % arg; // feed the next argument
std::clog << msg << std::endl; // print the formatted message
};
this->submit(std::move(task));
}
};
Това е всичко, което е необходимо. Вече можем да използваме Logger
като в този пример. Важно е всички работни нишки да бъдат join()
редактирани, преди Logger
да бъде унищожен или той няма да обработи всички съобщения.
int
main()
{
Logger logger {};
std::vector<std::thread> threads {};
std::random_device rnddev {};
for (int i = 0; i < 4; ++i)
{
const auto seed = rnddev();
auto task = [&logger, i, seed](){
std::default_random_engine rndeng {seed};
std::uniform_real_distribution<double> rnddist {0.0, 0.5};
for (double p = 0.0; p < 1.0; p += rnddist(rndeng))
logger.log("thread #%d is %6.2f %% done", {i, 100.0 * p});
logger.log("thread #%d has completed its work", {i});
};
threads.emplace_back(std::move(task));
}
for (auto& thread : threads)
thread.join();
}
Възможен изход:
thread #1 is 0.00 % done
thread #0 is 0.00 % done
thread #0 is 26.84 % done
thread #0 is 76.15 % done
thread #3 is 0.00 % done
thread #0 has completed its work
thread #3 is 34.70 % done
thread #3 is 78.92 % done
thread #3 is 91.89 % done
thread #3 has completed its work
thread #1 is 26.98 % done
thread #1 is 73.84 % done
thread #1 has completed its work
thread #2 is 0.00 % done
thread #2 is 10.17 % done
thread #2 is 29.85 % done
thread #2 is 79.03 % done
thread #2 has completed its work
person
5gon12eder
schedule
21.02.2015
vfprintf
и предаване наva_list
? - person nullptr   schedule 21.02.2015lock_guard
забавя нишките. - person Peter VARGA   schedule 21.02.2015va_list
в структура, която да мога да го използвам в нишката log? - person Peter VARGA   schedule 21.02.2015int
,double
и може би някакъв низ,std::vector
от някоиunion
(или нещо катоboost::variant
) може да свърши работа. - person 5gon12eder   schedule 21.02.2015