On 30/07/2014 07:44, Klaim - Joël Lamotte wrote:
I would like to ask Boost developers and users:
1. Am I the only one seeing the following pattern in my concurrent systems code? 2. Is there already some tools available in Boost that does what I describe here and that I am not aware of? 3. If not, would there be any interest in the tool I'm describing here or not? Is it a dumb idea? (I keep getting back to this idea but I'm surprised not finding anything similar online)
The pattern that I am seeing occurs when: 1. I have a value or a set of values in an object or several objects which value(s) describe some kind of configuration that several other systems will read and use. 2. The object(s) will be updated only by one thread (at a time). 3. When the object's value is modified, all the systems that rely on the object's value have to be notified so that they update themselves using that value. 4. The systems I am refering to can use different threads for updating and we can assume that they are running concurrently. 5. It is not important that the source object's value is synched with the reader's values at all time, however it is important that the sequence of changes to the source object's value is signaled to the readers exactly in the same order and "as fast as possible", but delay is ok.
This sort of thing is a publisher-subscriber model. There are lots of different implementations of it, depending on what each end "knows" (eg. push vs. pull) and who you want to have responsible for copying the data, whether you want lock-based or lock-free, etc. A really simple (albeit heavy-handed) way of doing this lock-free (or almost lock-free, depending on shared_ptr implementation) is to have your data in a shared_ptr; whenever something wants to act on the data it uses atomic_load to fetch it from some well-known location and can then do whatever read-only access it wishes; when it wants to change the data it first copies the entire object, makes the changes in the copy, and then uses atomic_compare_exchange to replace the "real" version. If the compare fails, it lost the race to a faster writer so must perform its updates again on the new data and try exchanging again. But that pattern is only good for fairly self-contained objects where reads are frequent and writes are rare, and where it's relatively easy to copy, throw away and recalculate (which also implies self-containment, because that gets more complex if you want to act on two or more such objects). In particular it is *not* suited to frequent concurrent writes because it's vulnerable to writer starvation (*some* writer will always succeed, but it's possible that one writer always fails because a faster one always beats it). There are other patterns that are better suited to other kinds of accesses; it's hard to find something generally applicable without making performance compromises.