借shared_ptr实现copy-on-write
场景:
一个多线程的C++程序,24h x 5.5d运行。有几个工作线程ThreadW{0,1,2,3},处理客户发过来的交易请求,另外有一个背景线程ThreadB,不定期更新程序内部的参考数据。这些线程都跟一个hash表打交道,工作线程只读,背景线程读写,必然要用到一些同步机制,防止数据损坏。这里的示例代码用std::map代替hash表,意思是一样的:
typedef map<string, vector<pair<string, int> > > Map;
map 的 key 是用户名,value 是一个vector,里边存的是不同stock的最小交易间隔,vector已经排好序,可以用二分查找。
我们的系统要求工作线程的延迟尽可能小,可以容忍背景线程的延迟略大。一天之内,背景线程对数据更新的次数屈指可数,最多一小时一次,更新的数据来自于网络,所以对更新的及时性不敏感。Map的数据量也不大,大约一千多条数据。
最简单的同步办法,用读写锁,工作线程加读锁,背景线程加写锁。但是读写锁的开销比普通mutex要大,如果工作线程能用最普通的非重入Mutex实现同步,就不必用读写锁,性能较高。我们借助shared_ptr实现了这一点:
class Mutex;
class MutexLockGuard;
class CustomerData
{
public:
CustomerData() : data_(new Map)
{ }
~CustomerData();
int query(const string& customer, const string& stock) const
{
MapPtr data = getData();
// data 一旦拿到,就不再需要锁了。取数据的时候只有getData()内部有锁,多线程并发读的性能很好。
// 假设用户肯定存在
const EntryList& entries = (*data)[customer];
return findEntry(entries, stock);
}
void update(const string& customer, const EntryList& entries );
private:
typedef vector<string, int> EntryList;
typedef map<string, EntryList> Map;
typedef tr1::shared_ptr<Map> MapPtr;
static int findEntry(const EntryList& entries, const string& key) const
{ /* 用 lower_bound 在 entries 里找 key */ }
MapPtr getData() const
{
MutexLockGuard lock(dataMutex_);
return data_;
}
MapPtr data_;
mutable Mutex dataMutex_;
};
关键看CustomerData::update()怎么写。既然要更新数据,那肯定得加锁,如果这时候其他线程正在读,那么不能在原来的数据上修改,得创建一个副本,在副本上修改,修改完了再替换。如果没有用户在读,那么就能直接修改,节约一次拷贝。
void CustomerData::update(const string& customer, const EntryList& entries )
{
MutexLockGuard lock(dataMutex_);
if (!data_.unique())
{
MapPtr newData(new Map(*data_));
data_.swap(newData);
}
assert(data_.unique());
(*data_)[customer] = entries;
}
注意其中用了shared_ptr::unique()来判断是不是有人在读,如果有人在读,那么我们不能直接修改,因为query()并没有全程加锁,只在getData()内部有锁。shared_ptr::swap()把data_替换为新副本,而且我们还在锁里,不会有别的线程来读,可以放心地更新。
据我们测试,大多数情况下更新都是在原来数据上进行的,拷贝的比例还不到1%,很高效。更准确的说,这不是copy-on-write,而是copy-on-other-reading。
我们将来可能会采用无锁数据结构,不过目前这个实现已经非常好,满足我们的要求。
分享到:
相关推荐
参考陈硕的多线程服务端编程>>,中的用shared_ptr实现copy-on-write技术,不过这里的线程采用的是c++11的线程库
2.8借shared_ptr 实现copy-on-write. . . . . . . . . . . . . . . . . . . . . . 52 第3章多线程服务器的适用场合与常用编程模型 3.1进程与线程. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . ...
写时拷贝(Copy-on-Write,COW)则是在尝试修改对象时才进行拷贝,如果只是读取,则可以共享同一份数据,节省内存。 在C++中,我们可以通过使用`std::shared_ptr`或`std::atomic`等工具来实现这一策略。以下是一些...
5. **Copy-On-Write机制**:在C++中,可以利用智能指针如`std::shared_ptr`来实现Cow策略。当节点被修改时,我们可以创建一个新的`std::shared_ptr`实例,将原节点的引用转移到新实例,然后修改新实例中的节点。 6....
在连接字符串之前和之后,我们可以检查 `RSTRING(str)->aux.shared` 的值来观察 copy-on-write 的发生。 总结来说,Ruby 的 String 类型强大之处在于它的可变性以及对性能的优化。通过内部结构的巧妙设计,Ruby ...
- **优化字符串复制**:对于频繁复制的场景,可以考虑引入“写时复制”(Copy-On-Write, COW)机制来提高效率。 - **预分配空间**:对于预期会增长的字符串,可以在构造函数中预分配一定的额外空间,以减少后续重新...
智能指针(如`std::unique_ptr`、`std::shared_ptr`)是C++11引入的,它们自动管理内存,防止内存泄漏。 八、多态 C++的多态性主要通过虚函数和抽象类实现,允许子类重写父类的方法。基类指针可以指向派生类对象,...
智能指针(如`std::shared_ptr`和`std::unique_ptr`)可以帮助自动管理资源生命周期,减少内存泄漏的风险。 - **CPPlint** CPPlint是一个静态代码分析工具,用于检查C++代码是否符合Google的编程规范。使用这类...
Classes Doing Work in Constructors Default Constructors Explicit Constructors Copy Constructors Structs vs. Classes Inheritance Multiple Inheritance Interfaces Operator Overloading Access Control ...
the Switch() operator is within a while loop, causing an error on the second iteration. (BZ 460) Disassembler - fix for error emitted for unknown type for target of scope operator. Now, ignore it and...
public void Write(SharedData data) { Marshal.StructureToPtr(data, ptr, false); } public SharedData Read() { return (SharedData)Marshal.PtrToStructure(ptr, typeof(SharedData)); } // 记得在...
**Smart Pointers**:智能指针(如`std::unique_ptr`, `std::shared_ptr`等)应当优先于原始指针使用,以避免内存泄漏和其他问题。 ##### 3. **cpplint**:谷歌提供了一个名为`cpplint`的工具,用于检查代码是否...
智能指针(如`std::shared_ptr`和`std::unique_ptr`)提供了资源自动管理和内存安全的能力。应当尽可能使用智能指针而不是原始指针。 #### 五、其他C++特性 **1. 引用参数(Reference Arguments)** 传递引用参数...
对于内存映射文件的写入,只需将`PAGE_READONLY`改为`PAGE_READWRITE`或`PAGE_WRITECOPY`,并在适当的地方调用`FlushViewOfFile`和`FlushFileBuffers`确保数据同步到磁盘。 此外,对于跨进程的数据共享,可以使用`...