Boost Mutex Scoped Lock
Asked Answered
D

2

18

I was reading through a Boost Mutex tutorial on drdobbs.com, and found this piece of code:

#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <iostream>

boost::mutex io_mutex;

void count(int id)
{
  for (int i = 0; i < 10; ++i)
  {
    boost::mutex::scoped_lock
      lock(io_mutex);
    std::cout << id << ": " <<
      i << std::endl;
  }
}

int main(int argc, char* argv[])
{
  boost::thread thrd1(
    boost::bind(&count, 1));
  boost::thread thrd2(
    boost::bind(&count, 2));
  thrd1.join();
  thrd2.join();
  return 0;
}

Now I understand the point of a Mutex is to prevent two threads from accessing the same resource at the same time, but I don't see the correlation between io_mutex and std::cout. Does this code just lock everything within the scope until the scope is finished?

Deflect answered 2/6, 2013 at 12:34 Comment(3)
Yes, scoped_lock locks the mutex until the scoped is exited. It has a rather intuitive name.Blackandwhite
@Blackandwhite I understand that part, scoped_lock makes perfect sense. What I don't get is the relationship between the mutex and anything else in the program. Is it more related to order of execution in the program, and less with actual memory? Sorry if this is a noob question, but I have been trying to learn mutexes for the past week or so and just not getting it.Deflect
@jlw2387: As I wrote in my answer, std::cout is a global object, so you can see that as a shared resource. Accessing a shared resource concurrently from different threads requires synchronization. This is what the scoped_lock is doingMegrim
M
23

Now I understand the point of a Mutex is to prevent two threads from accessing the same resource at the same time, but I don't see the correlation between io_mutex and std::cout.

std::cout is a global object, so you can see that as a shared resource. If you access it concurrently from several threads, those accesses must be synchronized somehow, to avoid data races and undefined behavior.

Perhaps it will be easier for you to notice that concurrent access occurs by considering that:

std::cout << x

Is actually equivalent to:

::operator << (std::cout, x)

Which means you are calling a function that operates on the std::cout object, and you are doing so from different threads at the same time. std::cout must be protected somehow. But that's not the only reason why the scoped_lock is there (keep reading).

Does this code just lock everything within the scope until the scope is finished?

Yes, it locks io_mutex until the lock object itself goes out of scope (being a typical RAII wrapper), which happens at the end of each iteration of your for loop.

Why is it needed? Well, although in C++11 individual insertions into cout are guaranteed to be thread-safe, subsequent, separate insertions may be interleaved when several threads are outputting something.

Keep in mind that each insertion through operator << is a separate function call, as if you were doing:

std::cout << id;
std::cout << ": ";
std::cout << i;
std::cout << endl;

The fact that operator << returns the stream object allows you to chain the above function calls in a single expression (as you have done in your program), but the fact that you are having several separate function calls still holds.

Now looking at the above snippet, it is more evident that the purpose of this scoped lock is to make sure that each message of the form:

<id> ": " <index> <endl>

Gets printed without its parts being interleaved with parts from other messages.

Also, in C++03 (where insertions into cout are not guaranteed to be thread-safe) , the lock will protect the cout object itself from being accessed concurrently.

Megrim answered 2/6, 2013 at 12:38 Comment(4)
I'm not sure that you've made it clear that the mutex locks a critical region of code---an execution path, and not any resources or objects. With regards to the thread safety of >> in pre-C++11: it depends on the implementation. Some were thread safe. And I'm rather surprised at your statement that std::cout<< i is thread safe in C++11. I can't see any reason for making it so. (I also cannot find where the standard guarantees it. But I don't know quite where to look, either.)Ketosis
@JamesKanze: That is specified in §27.4.1/4.Megrim
I wonder why. It doesn't buy you anything (as you quite rightly point out), and it goes against the general principles used elsewhere in the library. (A more useful approach would have been to provide a mutex member of the stream, so that even libraries with no knowledge of one another could agree on the mutex to use.)Ketosis
"the mutex locks a critical region of code---an execution path, and not any resources or objects" -- Wrong. The boost::mutex::scoped_lock constructor (the II part of RAII) locks the boost::mutex object passed to it (the RA part of RAII). Any number of code sections can be locked by the same mutex. e.g., the OP's program could write to cout in multiple places, each protected by the same mutex. "I wonder why." -- So that non-locked use of cout in multiple threads doesn't result in undefined behavior. Did you even read the section? "as you quite rightly point out" -- he did no such thing.Hike
K
9

A mutex has nothing to do with anything else in the program (except a conditional variable), at least at a higher level. A mutex has two effeccts: it controls program flow, and prevents multiple threads from executing the same block of code simultaneously. It also ensures memory synchronization. The important issue here, is that mutexes aren't associated with resources, and don't prevent two threads from accessing the same resource at the same time. A mutex defines a critical section of code, which can only be entered by one thread at a time. If all of the use of a particular resource is done in critical sections controled by the same mutex, then the resource is effectively protected by the mutex. But the relationship is established by the coder, by ensuring that all use does take place in the critical sections.

Ketosis answered 2/6, 2013 at 13:24 Comment(1)
queue<int>q_; q_.push(1); boost::mutex q_mutex; void func1() { { boost::mutex::scoped_lock start_stop_lock(q_mutex_); while(true) q_.push(1); } } int main() { boost::thread t1(func1()); //print top element std::cout << q_.top() << std::endl; } Consider the above case. Are you saying that the cout defined in the main function will be able to access q_ and print the value of q_.top() ?Asphyxiate

© 2022 - 2024 — McMap. All rights reserved.