0

I've been trying to learn how to multithread and came up with the following understanding. I was wondering if I'm correct or far off and, if I'm incorrect in any way, if someone could give me advice.

To create a thread, first you need to utilize a library such as <thread> or any alternative (I'm using boost's multithreading library to get cross-platform capabilities). Afterwards, you can create a thread by declaring it as such (for std::thread)

std::thread thread (foo);

Now, you can use thread.join() or thread.detach(). The former will wait until the thread finishes, and then continue; while, the latter will run the thread alongside whatever you plan to do.

If you want to protect something, say a vector std::vector<double> data, from threads accessing simultaneously, you would use a mutex.

Mutex's would be declared as a global variable so that they may access the thread functions (OR, if you're making a class that will be multithreaded, the mutex can be declared as a private/public variable of the class). Afterwards, you can lock and unlock a thread using a mutex.

Let's take a quick look at this example pseudo code:

std::mutex mtx;
std::vector<double> data;
void threadFunction(){
  // Do stuff
  // ...
  // Want to access a global variable
  mtx.lock();
  data.push_back(3.23);
  mtx.unlock();
  // Continue
}

In this code, when the mutex locks down on the thread, it only locks the lines of code between it and mtx.unlock(). Thus, other threads will still continue on their merry way until they try accessing data (Note, we would likely through a mutex in the other threads as well). Then they would stop, wait to use data, lock it, push_back, unlock it and continue. Check here for a good description of mutex's.

That's about it on my understanding of multithreading. So, am I horribly wrong or accurate?

Community
  • 1
  • 1
hherbol
  • 91
  • 12
  • 2
    Probably too broad a question for SO. "So, am I horribly wrong or accurate?" I'd say neither ;) – dyp Oct 16 '13 at 21:20
  • 2
    *Side remark:* **If** you use a mutex, you should probably use one of the [RAII locks](http://en.cppreference.com/w/cpp/thread/lock_guard) if possible. – dyp Oct 16 '13 at 21:22
  • 1
    You don't want to manual-lock that mutex. Where at all possible, you want to lock it with RAII to ensure any exceptions thrown don't leave any future waiters hanging in the wind with no place to go but back to the scheduler for another shot at waiting on something they can never get. – WhozCraig Oct 16 '13 at 21:29
  • Note that what you call «protection» can be achieved by synchronisation primitives, like spin locks, mutexes and semaphores, but also by using lock free algorithms and structures, using atomic operations, depending on the platform. – Macmade Oct 16 '13 at 21:30
  • Ya, that's about the gist of mutexes. What's your question? – dthorpe Oct 16 '13 at 21:32
  • @dthorpe, my question was primarily if my understanding of how mutexes work is right. I've found so many different implementations of threads and mutexes online that I got really confused and wanted some guidance from SO – hherbol Oct 16 '13 at 21:35
  • @DyP, thank you so much for the link to RAII locks. It looks like it'll be really helpful. Just a little confused as to why it's needed. @WhozCraig, you say I should use RAII to ensure 'exceptions thrown' will be dealt with appropriately. Does calling `std::lock_guard lock(g_i_mutex);` in DyP's provided link lock the whole thread? Or only when a variable is trying to be accessed multiple times? – hherbol Oct 16 '13 at 21:39
  • @hherbol Those RAII locks lock the mutex in their ctor and unlock it in their dtor. It's safer because when an exception is thrown while the mutex is locked, the RAII lock *object* is destroyed during the stack unwind. As it is destroyed, the dtor is called and the mutex will be unlocked. – dyp Oct 16 '13 at 21:46
  • I usually encapsulate thread, shared data with other threads and locking mechanism (e.g. mutex, or whatever is appropriate for the use case) inside a class (no global mutexes or such!! That's a bad idea for almost all cases). – πάντα ῥεῖ Oct 16 '13 at 22:01

1 Answers1

1

Your comments refer to "locking the whole thread". You can't lock part of a thread.

When you lock a mutex, the current thread takes ownership of the mutex. Conceptually, you can think of it as the thread places its mark on the mutex (stores its threadid in the mutex data structure). If any other thread comes along and attempts to acquire the same mutex instance, it sees that the mutex is already "claimed" by somebody else and it waits until the first thread has released the mutex. When the owning thread later releases the mutex, one of the threads that is waiting for the mutex can wake up, acquire the mutex for themselves, and carry on.

In your code example, there is a potential risk that the mutex might not be released once it is acquired. If the call to data.push_back(xxx) throws an exception (out of memory?), then execution will never reach mtx.unlock() and the mutex will remain locked forever. All subsequent threads that attempt to acquire that mutex will drop into a permanent wait state. They'll never wake up because the thread that owns the mutex is toast.

For this reason, acquiring and releasing critical resources like mutexes should be done in a manner that will guarantee they will be released regardless of how execution leaves the current scope. In other languages, this would mean putting the mtx.unlock() in the finally section of a try..finally block:

mtx.lock();
try
{
    // do stuff
}
finally
{
    mtx.unlock();
}

C++ doesn't have try..finally statements. Instead, C++ leverages its language rules for automatic disposal of locally defined variables. You construct an object in a local variable, the object acquires a mutex lock in its constructor. When execution leaves the current function scope, C++ will make sure that the object is disposed, and the object releases the lock when it is disposed. That's the RAII others have mentioned. RAII just makes use of the existing implicit try..finally block that wraps every C++ function body.

dthorpe
  • 35,318
  • 5
  • 75
  • 119
  • One more thing I want to clarify. When calling a mutex, you say it "locks the whole thread". So does it lock all threads and only run the one thread until it unlocks the mutex? – hherbol Oct 17 '13 at 02:27
  • 1
    You said "locks the whole thread", not I. The mutex only blocks other threads that attempt to acquire the mutex, when they attempt to acquire the mutex. If you have 500 threads and one of them acquires the mutex and the other 499 don't come anywhere near the mutex, then none of the 499 threads are in any way affected by the one thread which holds the mutex. If one of the 499 then takes an interest in the mutex and attempts to acquire it, that thread will block (do nothing) until the first thread releases the mutex. Score: 1 has mutex, 1 is waiting, and 498 don't care. – dthorpe Oct 17 '13 at 03:20
  • Thank you so much. That clears up a lot of confusion – hherbol Oct 17 '13 at 12:20
  • I think I figured it out and understand it better. If you'ld like to see it, I put the code on [code review](http://codereview.stackexchange.com/questions/32817/multithreading-c-loop). – hherbol Oct 17 '13 at 15:07