Lock granularity describes how much data one lock protects. A fine-grained lock protects a small amount; a coarse-grained lock protects more. Choose a scope large enough to protect a complete operation, but avoid making unrelated work wait for the same mutex. The amount of data protected and the time a lock is held are separate decisions. section 3.2.8
A stack operation split across calls
Suppose one thread checks `empty()`, reads `top()`, then calls `pop()`. Locking each call separately does not protect that three-call operation: another thread can change the stack between calls. A thread-safe interface can instead combine retrieval and removal in one `pop()` operation, with the mutex held throughout. Its copy constructor must likewise hold the source mutex across the copy. section 3.2.3
Table 3.1. A possible ordering of operations on a stack from two threads (view table figure)
| Thread A |
Thread B |
||
|---|---|---|---|
| if(!s.empty()) | |||
| if(!s.empty()) | |||
| int const value=s.top(); | |||
| int const value=s.top(); | |||
| s.pop(); | |||
| do_something(value); | s.pop(); | ||
| do_something(value); | |||
The two-thread ordering makes the gap between individually protected stack calls visible. section 3.2.3
Making one mutex protect everything avoids those gaps only by creating another problem: threads accessing different data must still take turns. An early multiprocessor Linux kernel used a single global kernel lock; later, finer-grained locking reduced contention. Finer-grained designs can, however, require more than one mutex to protect an operation. section 3.2.3
Keep processing outside the lock
Lock while obtaining shared data, unlock to process the local data, then lock again to write the result. `std::unique_lock` supports this explicit `unlock()` and `lock()` sequence. Avoid time-consuming file I/O while holding a mutex unless that mutex protects access to the file: waiting threads otherwise remain blocked for no useful reason. section 3.2.8
void get_and_process_data()
{
std::unique_lock<std::mutex> my_lock(the_mutex);
some_class data_to_process=get_next_data_chunk();
my_lock.unlock(); #1
result_type result=process(data_to_process);
my_lock.lock(); #2
write_result(data_to_process,result);
}
The function marks the two shared-data accesses and the processing performed between them without the lock. section 3.2.8
For a comparison of two objects, one option is to lock each object's mutex only long enough to read its value, then compare the copied values. This holds one mutex at a time; the reads are not one simultaneously locked operation. Decide whether that behavior protects the operation you actually need. section 3.2.8
