Now if the queue is empty when a thread executing `get()` wakes up (e.g., because another thread has gotton to `get()` before it),
...
...
@@ -11144,7 +11148,7 @@ and `thread` suspection and resumption are expensive.
do1(); // transaction: needs locking
do2(); // cleanup: does not need locking
}
Here, we are holding the lock for longer than necessary:
We should not have taken the lock before we needed it and should have released it again before starting the cleanup.
We could rewrite this to
...
...
@@ -11157,7 +11161,7 @@ We could rewrite this to
my_lock.unluck();
do2(); // cleanup: does not need locking
}
But that compromises safety and violates the [use RAII](#Rconc-raii) rule.
Instead, add a block for the critical section:
...
...
@@ -11170,7 +11174,7 @@ Instead, add a block for the critical section:
}
do2(); // cleanup: does not need locking
}
##### Enforcement
Impossible in general.
...
...
@@ -11188,7 +11192,7 @@ An unnamed local objects is a temporary that immediately goes out of scope.
unique_lock<mutex>(m1);
lock_guard<mutex> {m2};
lock(m1,m2);
This looks innocent enough, but it isn't.
##### Enforcement
...
...
@@ -11209,7 +11213,7 @@ It should be obvious to a reader that the data is to be guarded and how.
std::mutex m; // take this mutex before accessing other members
// ...
};
##### Enforcement
??? Possible?
...
...
@@ -11330,16 +11334,16 @@ It's error-prone and requires expert level knowledge of language features, machi
##### Example, bad
extern atomic<Link*> head; // the shared head of a linked list
extern atomic<Link*> head; // the shared head of a linked list
Link* nh = new Link(data,nullptr); // make a link ready for insertion
Link* h = head.load(); // read the shared head of the list
Link* nh = new Link(data,nullptr); // make a link ready for insertion
Link* h = head.load(); // read the shared head of the list
do {
if (h->data<=data) break; // if so, insert elsewhere
nh->next = h; // next element is the previous head
} while (!head.compare_exchange_weak(h,nh));// write nh to head or to h
if (h->data<=data) break; // if so, insert elsewhere
nh->next = h; // next element is the previous head
} while (!head.compare_exchange_weak(h,nh));// write nh to head or to h
Spot the bug.
It would be really hard to find through testing.
Read up on the ABA problem.
...
...
@@ -11390,7 +11394,7 @@ Become an expert before shipping lock-free code for others to use.
* Mark Batty, Scott Owens, Susmit Sarkar, Peter Sewell, and Tjark Weber, “Mathematizing C++ Concurrency”, POPL 2011.
* Damian Dechev, Peter Pirkelbauer, and Bjarne Stroustrup: Understanding and Effectively Preventing the ABA Problem in Descriptor-based Lock-free Designs. 13th IEEE Computer Society ISORC 2010 Symposium. May 2010.
* Damian Dechev and Bjarne Stroustrup: Scalable Non-blocking Concurrent Objects for Mission Critical Code. ACM OOPSLA'09. October 2009
* Damian Dechev, Peter Pirkelbauer, Nicolas Rouquette, and Bjarne Stroustrup: Semantically Enhanced Containers for Concurrent Real-Time Systems. Proc. 16th Annual IEEE International Conference and Workshop on the Engineering of Computer Based Systems (IEEE ECBS). April 2009.
* Damian Dechev, Peter Pirkelbauer, Nicolas Rouquette, and Bjarne Stroustrup: Semantically Enhanced Containers for Concurrent Real-Time Systems. Proc. 16th Annual IEEE International Conference and Workshop on the Engineering of Computer Based Systems (IEEE ECBS). April 2009.
### <a name="Rconc-double"></a>CP.110: Use a conventional pattern for double-checked locking
...
...
@@ -11410,7 +11414,7 @@ Double-checked locking is easy to mess up.
x_init.store(true, memory_order_release);
}
}
// ... use x ...
...
...
@@ -11435,7 +11439,7 @@ These rules defy simple catagorization:
##### Example
const volatile long clock;
This describes a register constantly updated by a clock circuit.
`clock` is `volatile` because its value will change without any action from the C++ program that uses it.
For example, reading `clock` twice will often yield two different values, so the optimizer had better not optimize away the second read in this code:
...
...
@@ -11443,7 +11447,7 @@ For example, reading `clock` twice will often yield two different values, so the
long t1 = clock;
// ... no use of clock here ...
long t2 = clock;
`clock` is `const` because the program should not try to write to `clock`.