utx::print, utx::wprint, etc. are thread-safe now, since Dec 24, 2023.
When utx::print is using a mutex, utx::print is absolutely 100% thread-safe. A mutex passed to utx::print will be constrained and matched by utx::param_mutex concept.
#include <utxcpp/core.hpp> #include <utxcpp/thread.hpp> std::mutex mutex; template <utx::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) utx::print(mutex, label, "i is", i); }; int main() { auto f1 = utx::async(fn<utx::ix32>, "int++++++", 'A', 'G'); auto f2 = utx::async(fn<utx::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 utx::print entire session is absolutely 100% thread-safe, but it loses some parallelism, because some unshared unique resources in a utx::print calling session do not need to be locked, two threads can use their own unshared unique resources parallelly.
#include <utxcpp/core.hpp> #include <utxcpp/thread.hpp> std::mutex mutex; template <utx::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 utx::print calling is protected by lock, // it is thread-safe, but some parallelism are lost. std::unique_lock<std::mutex> lock{mutex}; utx::print(label, "i is", i); } }; int main() { auto f1 = utx::async(fn<utx::ix32>, "int++++++", 'A', 'G'); auto f2 = utx::async(fn<utx::mtx>, "char---", 'A', 'G'); }
The default ostream of utx::print is std::cout, and the default ostream of utx::wprint is std::wcout, as std::cout and std::wcout are thread-safe by default, so using utx::print and utx::wprint by default is absolutely thread-safe.
#include <utxcpp/core.hpp> #include <utxcpp/thread.hpp> template <utx::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) { utx::print(label, "i is", i); } }; int main() { auto f1 = utx::async(fn<utx::ix32>, "int++++++", 'A', 'G'); auto f2 = utx::async(fn<utx::mtx>, "char---", 'A', 'G'); }
If no lock is used for utx::print, utx::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 <utxcpp/core.hpp> #include <utxcpp/thread.hpp> template <utx::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) { utx::print(label, "i is", i); } }; int main() { std::ostringstream buff; utx::print.set_ostream(buff); auto f1 = utx::async(fn<utx::ix32>, "int++++++", 'A', 'G'); auto f2 = utx::async(fn<utx::mtx>, "char---", 'A', 'G'); // Before calling .restore_ostream(), the execution of f1 and f2 must return. f1.wait(); f2.wait(); utx::print.restore_ostream(); utx::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.