PrevUpHome

esv::print Thread-Safe, cpp


It is shameful to hurt animals.

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

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

(esv::print) (esv::wprint)

Divided into several cases:

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

Use concept esv::param_mutex

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.

c++ Example

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

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 esv::print session entirely

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.

c++ Example

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

case: no lock for default ostream

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.

c++ Example

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

case: no lock for user-defined ostream

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.

c++ Example

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

Conclusion

See Also

esv::print

esv::wprint

esv::param_mutex


PrevUpHome

E