PrevUpHome

utx::print Thread-Safe, cpp


utx::print Thread-Safe cpp/c++

utx::print, utx::wprint, etc. are thread-safe now, since Dec 24, 2023.

(utx::print) (utx::wprint)

Divided into several cases:

case: Use Lock: by using concept utx::param_mutex

Use concept utx::param_mutex

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.

c++ Example

#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');
}

One Possilbe Output

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

case: Use Lock: by using standard lock and mutex

Use a standard lock to lock the utx::print session entirely

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.

c++ Example

#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');
}

case: no lock for default ostream

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.

c++ Example

#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');
}

case: no lock for user-defined ostream

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.

c++ Example

#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.

Conclusion

See Also

utx::print

utx::wprint

utx::param_mutex


PrevUpHome

utx::print

esv::print