// This data structure makes Read() almost lock-free by making Modify() // *much* slower. It's very suitable for implementing LoadBalancers which // have a lot of concurrent read-only ops from many threads and occasional // modifications of data. As a side effect, this data structure can store // a thread-local data for user. // // Read(): begin with a thread-local mutex locked then read the foreground // instance which will not be changed before the mutex is unlocked. Since the // mutex is only locked by Modify() with an empty critical section, the // function is almost lock-free. // // Modify(): Modify background instance which is not used by any Read(), flip // foreground and background, lock thread-local mutexes one by one to make // sure all existing Read() finish and later Read() see new foreground, // then modify background(foreground before flip) again.
同时,需要注意的是,这个 class 实现的时候利用了 TLS,所以使用的时候实际上可以自定义 TLS。
// Put foreground instance into ptr. The instance will not be changed until // ptr is destructed. // This function is not blocked by Read() and Modify() in other threads. // Returns 0 on success, -1 otherwise. intRead(ScopedPtr* ptr);
// Modify background and foreground instances. fn(T&, ...) will be called // twice. Modify() from different threads are exclusive from each other. // NOTE: Call same series of fn to different equivalent instances should // result in equivalent instances, otherwise foreground and background // instance will be inconsistent. template <typename Fn> size_tModify(Fn& fn); template <typename Fn, typename Arg1> size_tModify(Fn& fn, const Arg1&); template <typename Fn, typename Arg1, typename Arg2> size_tModify(Fn& fn, const Arg1&, const Arg2&);
// fn(T& background, const T& foreground, ...) will be called to background // and foreground instances respectively. template <typename Fn> size_tModifyWithForeground(Fn& fn); template <typename Fn, typename Arg1> size_tModifyWithForeground(Fn& fn, const Arg1&); template <typename Fn, typename Arg1, typename Arg2> size_tModifyWithForeground(Fn& fn, const Arg1&, const Arg2&); }
// _mutex will be locked by the calling pthread and DoublyBufferedData. // Most of the time, no modifications are done, so the mutex is // uncontended and fast. inlinevoidBeginRead(){ pthread_mutex_lock(&_mutex); }
template <typename T, typename TLS> template <typename Fn> size_t DoublyBufferedData<T, TLS>::Modify(Fn& fn) { // _modify_mutex sequences modifications. Using a separate mutex rather // than _wrappers_mutex is to avoid blocking threads calling // AddWrapper() or RemoveWrapper() too long. Most of the time, modifications // are done by one thread, contention should be negligible. BAIDU_SCOPED_LOCK(_modify_mutex); int bg_index = !_index.load(butil::memory_order_relaxed); // background instance is not accessed by other threads, being safe to // modify. constsize_t ret = fn(_data[bg_index]); if (!ret) { return0; }
// Publish, flip background and foreground. // The release fence matches with the acquire fence in UnsafeRead() to // make readers which just begin to read the new foreground instance see // all changes made in fn. _index.store(bg_index, butil::memory_order_release); bg_index = !bg_index; // Wait until all threads finishes current reading. When they begin next // read, they should see updated _index. { BAIDU_SCOPED_LOCK(_wrappers_mutex); for (size_t i = 0; i < _wrappers.size(); ++i) { _wrappers[i]->WaitReadDone(); } }