Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

Chapter 85: Mutexes

Section 85.1: Mutex Types

C++1x o ers a selection of mutex classes:

std::mutex - o ers simple locking functionality.

std::timed_mutex - o ers try_to_lock functionality

std::recursive_mutex - allows recursive locking by the same thread.

std::shared_mutex, std::shared_timed_mutex - o ers shared and unique lock functionality.

Section 85.2: std::lock

std::lock uses deadlock avoidance algorithms to lock one or more mutexes. If an exception is thrown during a call to lock multiple objects, std::lock unlocks the successfully locked objects before re-throwing the exception.

std::lock(_mutex1, _mutex2);

Section 85.3: std::unique_lock, std::shared_lock, std::lock_guard

Used for the RAII style acquiring of try locks, timed try locks and recursive locks.

std::unique_lock allows for exclusive ownership of mutexes.

std::shared_lock allows for shared ownership of mutexes. Several threads can hold std::shared_locks on a

std::shared_mutex. Available from C++ 14.

std::lock_guard is a lightweight alternative to std::unique_lock and std::shared_lock.

#include <unordered_map> #include <mutex> #include <shared_mutex> #include <thread> #include <string> #include <iostream>

class PhoneBook { public:

std::string getPhoneNo( const std::string & name )

{

std::shared_lock<std::shared_timed_mutex> l(_protect); auto it = _phonebook.find( name );

if ( it != _phonebook.end() ) return (*it).second;

return "";

}

void addPhoneNo ( const std::string & name, const std::string & phone )

{

std::unique_lock<std::shared_timed_mutex> l(_protect); _phonebook[name] = phone;

}

std::shared_timed_mutex _protect; std::unordered_map<std::string,std::string> _phonebook;

GoalKicker.com – C++ Notes for Professionals

457

};

Section 85.4: Strategies for lock classes: std::try_to_lock, std::adopt_lock, std::defer_lock

When creating a std::unique_lock, there are three di erent locking strategies to choose from: std::try_to_lock, std::defer_lock and std::adopt_lock

1. std::try_to_lock allows for trying a lock without blocking:

{

std::atomic_int temp {0}; std::mutex _mutex;

std::thread t( [&](){

while( temp!= -1){ std::this_thread::sleep_for(std::chrono::seconds(5)); std::unique_lock<std::mutex> lock( _mutex, std::try_to_lock);

if(lock.owns_lock()){ //do something temp=0;

}

}

});

while ( true )

{

std::this_thread::sleep_for(std::chrono::seconds(1)); std::unique_lock<std::mutex> lock( _mutex, std::try_to_lock); if(lock.owns_lock()){

if (temp < INT_MAX){ ++temp;

}

std::cout << temp << std::endl;

}

}

}

2.std::defer_lock allows for creating a lock structure without acquiring the lock. When locking more than one mutex, there is a window of opportunity for a deadlock if two function callers try to acquire the locks at the same time:

{

std::unique_lock<std::mutex> lock1(_mutex1, std::defer_lock); std::unique_lock<std::mutex> lock2(_mutex2, std::defer_lock); lock1.lock()

lock2.lock(); // deadlock here std::cout << "Locked! << std::endl; //...

}

With the following code, whatever happens in the function, the locks are acquired and released in appropriate order:

{

std::unique_lock<std::mutex> lock1(_mutex1, std::defer_lock); std::unique_lock<std::mutex> lock2(_mutex2, std::defer_lock);

GoalKicker.com – C++ Notes for Professionals

458

std::lock(lock1,lock2); // no deadlock possible std::cout << "Locked! << std::endl;

//...

}

3. std::adopt_lock does not attempt to lock a second time if the calling thread currently owns the lock.

{

std::unique_lock<std::mutex> lock1(_mutex1, std::adopt_lock); std::unique_lock<std::mutex> lock2(_mutex2, std::adopt_lock); std::cout << "Locked! << std::endl;

//...

}

Something to keep in mind is that std::adopt_lock is not a substitute for recursive mutex usage. When the lock goes out of scope the mutex is released.

Section 85.5: std::mutex

std::mutex is a simple, non-recursive synchronization structure that is used to protect data which is accessed by multiple threads.

std::atomic_int temp{0}; std::mutex _mutex;

std::thread t( [&](){

while( temp!= -1){ std::this_thread::sleep_for(std::chrono::seconds(5)); std::unique_lock<std::mutex> lock( _mutex);

temp=0;

}

});

while ( true )

{

std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::unique_lock<std::mutex> lock( _mutex, std::try_to_lock); if ( temp < INT_MAX )

temp++;

cout << temp << endl;

}

Section 85.6: std::scoped_lock (C++ 17)

std::scoped_lock provides RAII style semantics for owning one more mutexes, combined with the lock avoidance algorithms used by std::lock. When std::scoped_lock is destroyed, mutexes are released in the reverse order from which they where acquired.

{

std::scoped_lock lock{_mutex1,_mutex2}; //do something

}

GoalKicker.com – C++ Notes for Professionals

459