std::thread
class (available from the <thread>
header file), which can work with regular functions, lambdas and functors. In addition, it allows you to pass any number of parameters to a stream function. #include <thread> void threadFunction() { // do smth } int main() { std::thread thr(threadFunction); thr.join(); return 0; }
thr
is an object representing the thread in which the threadFunction()
function will be executed. A call to join
blocks the calling thread (in our case, the main thread) until thr
(or rather threadFunction()
) does its work. If the stream function returns a value, it will be ignored. However, the function can accept any number of parameters. void threadFunction(int i, double d, const std::string &s) { std::cout << i << ", " << d << ", " << s << std::endl; } int main() { std::thread thr(threadFunction, 1, 2.34, "example"); thr.join(); return 0; }
std::ref
or std::cref
, as in the example: void threadFunction(int &a) { a++; } int main() { int a = 1; std::thread thr(threadFunction, std::ref(a)); thr.join(); std::cout << a << std::endl; return 0; }
std::ref
, the result of the program will be 1.join
method, you should consider another, a similar method - detach
.detach
allows you to disconnect the stream from the object, in other words, to make it background. join
can no longer be applied to detached threads. int main() { std::thread thr(threadFunction); thr.detach(); return 0; }
try { std::thread thr1(threadFunction); std::thread thr2(threadFunction); thr1.join(); thr2.join(); } catch (const std::exception &ex) { std::cout << ex.what() << std::endl; }
std::mutex g_mutex; std::vector<std::exception_ptr> g_exceptions; void throw_function() { throw std::exception("something wrong happened"); } void threadFunction() { try { throw_function(); } catch (...) { std::lock_guard<std::mutex> lock(g_mutex); g_exceptions.push_back(std::current_exception()); } } int main() { g_exceptions.clear(); std::thread thr(threadFunction); thr.join(); for(auto &e: g_exceptions) { try { if(e != nullptr) std::rethrow_exception(e); } catch (const std::exception &e) { std::cout << e.what() << std::endl; } } return 0; }
<thread>
in the std::this_thread
:g_exceptions
vector to make sure that only one thread could insert a new item at a time. For this, I used a mutex and a lock on the mutex. The mutex is a basic synchronization element and in C ++ 11 is presented in 4 forms in the <mutex>
header file:std::mutex
with the help functions get_id()
and sleep_for()
mentioned earlier: #include <iostream> #include <chrono> #include <thread> #include <mutex> std::mutex g_lock; void threadFunction() { g_lock.lock(); std::cout << "entered thread " << std::this_thread::get_id() << std::endl; std::this_thread::sleep_for(std::chrono::seconds(rand()%10)); std::cout << "leaving thread " << std::this_thread::get_id() << std::endl; g_lock.unlock(); } int main() { srand((unsigned int)time(0)); std::thread t1(threadFunction); std::thread t2(threadFunction); std::thread t3(threadFunction); t1.join(); t2.join(); t3.join(); return 0; }
entered thread 10144 leaving thread 10144 entered thread 4188 leaving thread 4188 entered thread 3424 leaving thread 3424
lock
method, and after finishing working with shared data it must be unlocked.std::vector
), having add()
methods for adding one element and addrange()
for adding several elements.va_args
. Also, the dump()
method should not belong to the container, but should be an autonomous function. The purpose of this example is to show the basic concepts of using mutexes, and not to make a full-fledged, error-free, thread-safe container. template <typename T> class container { std::mutex _lock; std::vector<T> _elements; public: void add(T element) { _lock.lock(); _elements.push_back(element); _lock.unlock(); } void addrange(int num, ...) { va_list arguments; va_start(arguments, num); for (int i = 0; i < num; i++) { _lock.lock(); add(va_arg(arguments, T)); _lock.unlock(); } va_end(arguments); } void dump() { _lock.lock(); for(auto e: _elements) std::cout << e << std::endl; _lock.unlock(); } }; void threadFunction(container<int> &c) { c.addrange(3, rand(), rand(), rand()); } int main() { srand((unsigned int)time(0)); container<int> cntr; std::thread t1(threadFunction, std::ref(cntr)); std::thread t2(threadFunction, std::ref(cntr)); std::thread t3(threadFunction, std::ref(cntr)); t1.join(); t2.join(); t3.join(); cntr.dump(); return 0; }
unlock
), which is impossible. This is where std::recursive_mutex
comes on stage, which allows you to get the same mutex several times. The maximum number of receiving mutexes is not defined, but if this number is reached, lock
will throw an exception std :: system_error . Therefore, the solution to the problem in the code above (except for changing the implementation of addrange()
so that lock
and unlock
not called) is to replace the mutex with std::recursive_mutex
. template <typename T> class container { std::recursive_mutex _lock; // ... };
6334 18467 41 6334 18467 41 6334 18467 41
threadFunction()
, the same numbers are generated. This is because the void srand (unsigned int seed);
function void srand (unsigned int seed);
initializes the seed
only for the main thread. In other threads, the pseudo-random number generator is not initialized and the same numbers are obtained each time.lock()
), and when an object is destroyed, it automatically releases the mutex (by calling unlock()
)lock_guard
, also supports pending locking, temporary locking, recursive locking, and using conditional variables template <typename T> class container { std::recursive_mutex _lock; std::vector<T> _elements; public: void add(T element) { std::lock_guard<std::recursive_mutex> locker(_lock); _elements.push_back(element); } void addrange(int num, ...) { va_list arguments; va_start(arguments, num); for (int i = 0; i < num; i++) { std::lock_guard<std::recursive_mutex> locker(_lock); add(va_arg(arguments, T)); } va_end(arguments); } void dump() { std::lock_guard<std::recursive_mutex> locker(_lock); for(auto e: _elements) std::cout << e << std::endl; } };
dump()
method must be constant, because it does not change the state of the container. Try to make it so and get an error when compiling: 'std::lock_guard<_Mutex>::lock_guard(_Mutex &)' : cannot convert parameter 1 from 'const std::recursive_mutex' to 'std::recursive_mutex &'
lock()
and unlock()
methods. Thus, the lock_guard
argument cannot be a constant. The solution to this problem is to make the mutable
, then the const specifier will be ignored and this will allow changing the state from constant functions. template <typename T> class container { mutable std::recursive_mutex _lock; std::vector<T> _elements; public: void dump() const { std::lock_guard<std::recursive_mutex> locker(_lock); for(auto e: _elements) std::cout << e << std::endl; } };
defer_lock
type defer_lock_t
: do not receive mutextry_to_lock
type try_to_lock_t
: try to get a mutex without blockingadopt_lock
type adopt_lock_t
: it is assumed that the calling thread already has a mutex struct defer_lock_t { }; struct try_to_lock_t { }; struct adopt_lock_t { }; constexpr std::defer_lock_t defer_lock = std::defer_lock_t(); constexpr std::try_to_lock_t try_to_lock = std::try_to_lock_t(); constexpr std::adopt_lock_t adopt_lock = std::adopt_lock_t();
std
also provides several methods for locking one or more mutexes:lock()
, try_lock()
and unlock()
)exchange()
function that swaps two elements of different containers. For thread safety, the function synchronizes access to these containers, getting the mutex associated with each container. template <typename T> class container { public: std::mutex _lock; std::set<T> _elements; void add(T element) { _elements.insert(element); } void remove(T element) { _elements.erase(element); } }; void exchange(container<int> &c1, container<int> &c2, int value) { c1._lock.lock(); std::this_thread::sleep_for(std::chrono::seconds(1)); // deadlock c2._lock.lock(); c1.remove(value); c2.add(value); c1._lock.unlock(); c2._lock.unlock(); }
int main() { srand((unsigned int)time(NULL)); container<int> cntr1; cntr1.add(1); cntr1.add(2); cntr1.add(3); container<int> cntr2; cntr2.add(4); cntr2.add(5); cntr2.add(6); std::thread t1(exchange, std::ref(cntr1), std::ref(cntr2), 3); std::thread t2(exchange, std::ref(cntr2), std::ref(cntr1), 6); t1.join(); t2.join(); return 0; }
std::lock
, which guarantees locking in a safe (in terms of deadlock) way: void exchange(container<int> &c1, container<int> &c2, int value) { std::lock(c1._lock, c2._lock); c1.remove(value); c2.add(value); c1._lock.unlock(); c2._lock.unlock(); }
Source: https://habr.com/ru/post/182610/
All Articles