Itβs hard to say what you do that causes problems. The mutex structure is pretty simple. Mutex lock, access to shared data, mutex unlock. This protects the data because the mutex will only allow one thread to get the lock at a time. Any thread that cannot get a lock must wait for the mutex to be unlocked. Unlocking awakens the waiters. Then they will fight to reach the castle. Losers come back to sleep. The time required to wake up may be several ms or more from the moment the lock is released. Make sure you always unlock mutexes.
Make sure you do not lock the locks for an extended period of time. In most cases, a long period of time is like a microsecond. I prefer to keep it around "a few lines of code." That's why people suggested you do a long calculation outside the lock. The reason that you do not hold locks for a long time is to increase the number of cases where other threads fall into the lock and must spin or sleep, which reduces performance. You also increase the likelihood that your thread may be previously missed when you own the lock, which means that the lock is turned on when that thread goes to sleep. This is even worse.
Threads that do not have a lock should not sleep. Spinning means that a thread that encounters a locked mutex is not sleeping, but the loops repeatedly check the lock for a predetermined period before giving up and sleeping. This is a good idea if you have multiple cores or cores capable of multiple simultaneous threads. Multiple active threads mean that two threads can execute code at the same time. If the lock is around a small amount of code, then the thread that received the lock will be made in the near future. another thread should only wait for a couple of nano secs before it gets a lock. Remember that a sleeping thread is a context switch and some code for attaching your thread to waiters on a mutex, all costs. In addition, as soon as your thread sleeps, you need to wait a while before the scheduler starts waking it. it may be a few ms. Search spindle blocks.
If you have only one core, then if the thread encounters a lock, this means that the other sleeping thread owns the lock and no matter how long you start spinning, not unlocking it. Thus, you will use a lock that immediately weaves the waiter, hoping that the thread that owns the lock wakes up and ends.
You should assume that the stream can be unloaded with any machine code instruction. You should also assume that each line of c code probably contains many machine code instructions. A classic example is i ++. This is one statement in c, but read, increment and store in machine code.
If you really care about performance, try using atomic operations first. Look at mutexes as a last resort. Most concurrency problems are easily solved with atomic operations (google gcc atomic operations to start learning), and very few problems really need mutexes. Mutex is a slower way.
Protect your shared data wherever it is written , and wherever it is read . otherwise ... prepare for failure. You do not need to protect shared data during periods of time when only one thread is active.
Its often useful to be able to run an application with 1 thread as well as N threads. Thus, you can more easily debug race conditions.
Minimize the shared data that you protect with locks. Try to organize the data in the structures so that one thread can get exclusive access to the whole structure (perhaps by setting one blocked flag or version number or both), and after that do not worry about anything. Then most of the code is not cluttered with locks and race conditions.
Functions that are ultimately written to shared variables must use temporary variables until the last moment, and then copy the results. Not only does the compiler generate more efficient code, but access to shared variables, especially changing them, leads to updating lines in caches between L2 and main RAM and all kinds of other performance problems. Again, if you do not care about performance, do not pay attention to it. However, I recommend you google the document "everything a programmer should know about memory" if you want to know more.
If you are reading a single variable from general data, you probably do not need to block as long as the variable is an integer type and not a member of a bit field (bit field members are read / written with several instructions). Read about atomic operations. When you need to deal with multiple values, you will need a lock to make sure that you have not read version A of a single value, unload, and then read version B of the next value. The same is true for writing.
You will find that copies of data, even copies of entire structures, come in handy. You can work on creating a new copy of the data, and then change it by changing the pointer to one atomic operation. You can make a copy of the data, and then perform calculations on it without worrying if it changes.
So, perhaps what you want to do:
lock the mutex Make a copy of the input data to the long running calculation. unlock the mutex L1: Do the calculation Lock the mutex if the input data has changed and this matters read the input data, unlock the mutex and go to L1 updata data unlock mutex
Perhaps in the example above you still save the result if the input has changed, but go back and recalc. It depends on whether other threads can use a slightly outdated answer. Perhaps other threads, when they see that the thread is already doing the calculation, just change the input and leave it occupied by the thread to notice this and repeat the calculation (there will be a race condition that you will need to handle if you do this, and simple ) Thus, other threads can do other work, rather than just sleep.
greetings.