Hacker News new | ask | show | jobs
by j_seigh 370 days ago
You just need to keep track of each thread's quiescent states however they are implemented.
1 comments

Right but isn't that difficult to implement in a library?
Thread local vars would be used with lazy initialization. Clean up might be a little tricky depending on what implementation of thread local you use. Thread local support is not as great as it should be.
Not really. https://github.com/nicowilliams/ctp is mine -- the API is not RCU, but a simpler and easier to use "thread-safe variable" construct. You'll find two C implementations there. I've been using this in production for almost a decade without any trouble. (For a long time I had a missing producer membar in the create primitive (oops!), but because it only happens once it never caused me any problems.)

To be fair I thought about it for a long time first. But nowadays these techniques are well known, so now it's easier to "copy" the thinking others have done, and that thinking is the hard part.

I took a brief look at your link. How does this even qualify to be RCU? The read-copy-update paradigm requires that the update step be able to know whether the value to be updated is derived from the recent copy. The set function here doesn't even allow you to atomically compare your read version against the current version. Imagine you are implementing a simple array with RCU; you read the array, make a copy, append to the array, and then you need to atomically compare the array pointer pre-append and set the array just so that you don't lose writes by other threads.
The get function always returns you a stable value if at least one value was ever written (else you get NULL, natch).

The set function will not invalidate any values seen by get that might still be referenced, but the new value will be seen by all subsequent gets.

Old values will be destructed when no longer referenced.

To read-copy-update you'll need to take a lock (that readers don't), get the current value (it will be the current one given you're locking out other writers), copy it, modify the copy, then set the new value.

You don't have to read-copy-update if the new values don't depend on the preceding values. So my API does not force read-copy-update, but you can do RCU with it.

Got it. So basically you want writers to use their own lock to coordinate writes. Okay, then this makes it inconvenient to deal with readers that could possibly but infrequently become writers. They have to do a second read under a lock to write. And if one chooses a data structure for which copy isn't a cheap operation, this can be slower than classic RCU.
> Okay, then this makes it inconvenient to deal with readers that could possibly but infrequently become writers. They have to do a second read under a lock to write.

Well, first of all reads are real cheap, so that's not a big concern. In some systems readers don't ever turn around to write, so that's ok, and in others they don't modify the current value. E.g., in one system I've built we use TinyCDB files and when new files are renamed into place we open those and set the handle on one of these thread-safe variables -- no read-modify involved here.

> And if one chooses a data structure for which copy isn't a cheap operation, this can be slower than classic RCU.

Either way it's RCU. It's best to publish immutable data with RCU and design data structures where copy-on-write modifications don't have to copy the whole data structure but rather just small parts of it. E.g., in a tree you should only have to copy the interior nodes from the one you're modifying to the root.

One can always build a hash table where each cell is one of these thread-safe variables (or plain old RCU), though one would have to make these thread-safe variables lighter weight to make having lots of them realistic (currently my implementation has a pthread_key_t per variable, which does not scale).

In TFA ArcSwap is essentially indistinguishable from a read-only hash table published on something like one of my thread-safe variables, and for TFA this worked better than the available alternatives in spite of the copy-on-write burden.

BTW, I built this thing originally to allow global configuration to be shared with otherwise-shared-nothing threads and allow the service to be reconfigurable at run-time. Without something like this (or plain old RCU) your options are limited to things like using read-write locks (which are way worse than anything you can imagine being bad about RCU used with immutable data structures). I was very displeased with read-write locks when I needed to solve this particular problem at Sun, but I didn't build this thing until after I left Sun (Oracle).

Nowadays RCU-ish libraries are pretty widely available. OpenSSL 3.3 (or was it 3.4?) has something like this after the threaded performance debacles of 3.0. In particular OpenSSL has an RCU-ish hash table. You might be interested in it. IIRC (but it's been a while since I looked) it uses one hazard-pointer-per-thread to acquire a stable reference to a key or value long enough to then incref a refcount -- pretty clever, and that's essentially the way to make a "thread-safe variable" light-weight enough to be able to use one for each slot in a hash table.