#include <thread>
void threadFunction() {}
int main()
{
std::thread thread(threadFunction);
thread.join();
return 0;
}
Now, let's talk about synchronization, starting with Mutex. Mutex is a quite simple synchronization primitive meant for protecting a resource from accessing by multiple threads at the same time. Here is what it looks like:
#include <thread>
#include <mutex>
std::mutex mutex;
int resource = 2;
void threadFunction()
{
std::lock_guard<std::mutex> lock(mutex);
resource *= 2;
}
int main()
{
std::thread thread1(threadFunction);
std::thread thread2(threadFunction);
thread1.join();
thread2.join();
return 0;
}
As you can see from the example above we successfully protected our resource variable from accessing by multiple threads using mutex. You probably noticed that we used std::lock_guard template class to lock our mutex, well std::lock_guard is that exact lock wrapper we used to make ourselves before C++11 arrival. If you forgot how it works, its very simple. It locks mutex upon construction and then releases it upon destruction when we leave function body scope. You may be wondering why cant you lock and unlock mutex whenever you need using std::lock_guard, well, thats because std::lock_guard follows so-called scope ownership which prevents you from managing your lock in any way. If you need to manage your lock you kinda have to use a different kinda lock which is std::unique_lock.
#include <thread>
#include <mutex>
std::mutex mutex;
int resource = 2;
void threadFunction()
{
std::unique_lock<std::mutex> lock(mutex);
resource *= 2;
lock.unlock();
lock.unlock();
}
int main()
{
std::thread thread1(threadFunction);
std::thread thread2(threadFunction);
thread1.join();
thread2.join();
return 0;
}
As you can see std::unique_lock is much more flexible when it comes to a lock management. Talking about flexibility, there is one more synchronization primitive I'd like to show you, which is a condition variable. Let's slightly modify our example to use this primitive:
#include <thread>
#include <mutex>
#include <condition_variable>
#include <condition_variable>
std::mutex mutex;
std::condition_variable condition_variable;
std::condition_variable condition_variable;
int resource = 2;
void threadFunction()
{
// Lock or if we can't block as usual
// Lock or if we can't block as usual
std::unique_lock<std::mutex> lock(mutex);
// Successfully acquired lock, now we can access our std::condition_variable.
// Calling wait method, which blocks this thread and releases lock.
condition_variable.wait(lock);
// Successfully acquired lock, now we can access our std::condition_variable.
// Calling wait method, which blocks this thread and releases lock.
condition_variable.wait(lock);
// If we got here it means main thread called condition_variable.notify_all(),
// which resulted in unblocking current thread and reacquiring lock.
// We can thread-safely mess with out beloved resource.
// which resulted in unblocking current thread and reacquiring lock.
// We can thread-safely mess with out beloved resource.
resource *= 2;
}
int main()
{
std::thread thread1(threadFunction);
std::thread thread2(threadFunction);
std::unique_lock<std::mutex> lock(mutex);
condition_variable.notify_all();
std::unique_lock<std::mutex> lock(mutex);
condition_variable.notify_all();
thread1.join();
thread2.join();
return 0;
}
Ok, now let me try to explain to you what's going on in here. First of, as you can see, we still use locks to protect our resource. Well, actually we protect both our resource and std::condition_variable whose purpose is to unblock all threads waiting on it and then reacquire locks upon notification from other thread, which, in our example, happens to be main thread. Yes, I know, my example is way too trivial to be practical, but it wasn't the goal here, I merely wanted to explain the idea.
No comments:
Post a Comment