It is shameful to hurt animals.
esv::print, esv::wprint, etc. are thread-safe now, since Dec 24, 2023.
When esv::print is using a mutex, esv::print is absolutely 100% thread-safe. A mutex passed to esv::print will be constrained and matched by esv::param_mutex concept.
#include <esvcpp/core.hpp> #include <esvcpp/thread.hpp> std::mutex mutex; template <esv::integral_meric value_xt> auto fn = [] (const std::string_view label, const value_xt & from, const value_xt & to) { for (auto i=from; i<=to; ++i) esv::print(mutex, label, "i is", i); }; int main() { auto f1 = esv::async(fn<esv::ix32>, "int++++++", 'A', 'G'); auto f2 = esv::async(fn<esv::mtx>, "char---", 'A', 'G'); }
char--- i is A char--- i is B char--- i is C char--- i is D char--- i is E int++++++ i is 65 char--- i is F char--- i is G int++++++ i is 66 int++++++ i is 67 int++++++ i is 68 int++++++ i is 69 int++++++ i is 70 int++++++ i is 71
Using a standard lock to lock the esv::print entire session is absolutely 100% thread-safe, but it loses some parallelism, because some unshared unique resources in a esv::print calling session do not need to be locked, two threads can use their own unshared unique resources parallelly.
#include <esvcpp/core.hpp> #include <esvcpp/thread.hpp> std::mutex mutex; template <esv::integral_meric value_xt> auto fn = [] (const std::string_view label, const value_xt & from, const value_xt & to) { for (auto i=from; i<=to; ++i) { // The entire session of esv::print calling is protected by lock, // it is thread-safe, but some parallelism are lost. std::unique_lock<std::mutex> lock{mutex}; esv::print(label, "i is", i); } }; int main() { auto f1 = esv::async(fn<esv::ix32>, "int++++++", 'A', 'G'); auto f2 = esv::async(fn<esv::mtx>, "char---", 'A', 'G'); }
The default ostream of esv::print is std::cout, and the default ostream of esv::wprint is std::wcout, as std::cout and std::wcout are thread-safe by default, so using esv::print and esv::wprint by default is absolutely thread-safe.
#include <esvcpp/core.hpp> #include <esvcpp/thread.hpp> template <esv::integral_meric value_xt> auto fn = [] (const std::string_view label, const value_xt & from, const value_xt & to) { for (auto i=from; i<=to; ++i) { esv::print(label, "i is", i); } }; int main() { auto f1 = esv::async(fn<esv::ix32>, "int++++++", 'A', 'G'); auto f2 = esv::async(fn<esv::mtx>, "char---", 'A', 'G'); }
If no lock is used for esv::print, esv::wprint, etc, and the output ostream is user-defined, the thread-safe depends on the user-defined ostream. In many cases, it is difficult to guarantee thread-safe.
#include <esvcpp/core.hpp> #include <esvcpp/thread.hpp> template <esv::integral_meric value_xt> auto fn = [] (const std::string_view label, const value_xt & from, const value_xt & to) { for (auto i=from; i<=to; ++i) { esv::print(label, "i is", i); } }; int main() { std::ostringstream buff; esv::print.set_ostream(buff); auto f1 = esv::async(fn<esv::ix32>, "int++++++", 'A', 'G'); auto f2 = esv::async(fn<esv::mtx>, "char---", 'A', 'G'); // Before calling .restore_ostream(), the execution of f1 and f2 must return. f1.wait(); f2.wait(); esv::print.restore_ostream(); esv::print(buff.view()); }
In this example, the user-defined ostream buff is used, running it for thousands of times is likely thread-safe, but I am not sure about one or two times, it might crash.