currenthashmap如何实现线程安全_接口中的魔鬼-如何设计一个多线程安全容器的接口...

b3f71535704db5f4b76d70bb72fca8bb.png

总所周知:

  • C++的STL中的容器都不是线程安全的
  • 使用mutex保护数据可以使其线程安全

那么使用mutex + stl 一定可以组装出一个线程安全的容器。

如下所示,我们可以这么实现一个线程安全的stack

#include 

我们可以在push,top, pop甚至empty的内部,使用mutex将stack s保护起来。使用std::lock_guard将mutex包起来,似乎一个线程安全的stack就完成了。

BUT!这样实现的stack并不是threadsafe的,魔鬼藏在细节中:

T& top();

top接口返回引用会使你精心保护的内部stack数据,泄漏到mutex的外部,从而导致在多线程访问时会出现问题,考虑如下情况:

319ac60fb208f954dc5e7f69d0e0fd7d.png

这样会导致额外的程序的崩溃,而对于下面这种情况,返回引用则会引起data race

b998b888c8605258f11a03ef3df63982.png

所以,在多线程时接口返回引用并不是一个好主意。

if (s.empty()) {
    s.push(1);
}

上面的代码在单线程中work良好,当stack为空时,便向stack中push一个元素。但是在多线程中,上述代码可能会出现问题:

d381cc66784c1e15b41b57737b5a9d74.png

可以看出由于empty+push非原子性操作,所以在多线程的情境下empty的结果并不可信。无法保证在push的时候empty的结果有效。

解决方案之一是在整个if 语句的外层增加一个mutex,将这部分代码保护起来。这种方案在某种程度上确实可以work,但是这这要求用户在使用的时候额外添加自己的锁,也不符合我们对于线程安全容器的定义。

在多线程的场景下,empty这种在没有更粗粒度的锁的情况下往往是无意义的。所以解决这一问题的另一方案就是提供语意多线程安全的接口,例如empty_push接口,如果stack为空,那么就push,否则就返回。这样我们通过在函数内部加锁可以使得这一操作多线程安全,而用户也无需增加额外的锁。

按照上述思路,我们可以组合出各种各样的接口,甚至丧心病狂的empty_push_pop_empty_push_pop()。当然这种方法在99.99999%的时候都是多余的。那么,如何才能设计出合理又简洁的接口呢?

hin遗憾,没有任何一个标准可以告诉你到底你应该提供哪些接口,如果非要又有一个,那就是看需求(手动狗头)。不过我们可以参考一下标准库的实现,看看他们提供了哪些接口。

第一个是C++的atomic,它是c++11中新加入的一个模板类型,可实例化为原子类型,它提供了如下的接口:

  • store 替换atomic对象的值
  • load 获取atomic对象的值
  • exchange 交换并获取atomic对象先前的值
  • compare-exchange-weak / compare-exchange-weak 比较原子与非原子的值,相等则交换,不相等则加载
  • add-fetch 将参数加到atomic对象,并返回先前的值

第二个是golang的sync.map ,它提供了一个线程安全的map:

  • load 获取key对应的value
  • store 设置key对应的value
  • loadOrStore key存在则load,不存在则store
  • delete 删除k,v
  • range 使用给定的func遍历map

通过比较,可以得出:

  1. 提供了store和load的方法,用于获取和设置数据
  2. 提供了功能相似的方法:compare-exchange vs. loadOrStore方法,提供了 加载-比较-操作-写回的功能
  3. 提供了各自个性化的方法:add-fetch vs. range

效仿这两个库,threadsafe_stack拟提供一下接口:

  • push
  • pop
  • top-exchange
  • empty_push

这样就能保证使用这不需要额外增加锁来保证接口的有效性。什么?你说有没有啥特有的方法?那你得去问问你得产品经理喽~

综上所述,对一个线程不安全的库进行线程安全的改造,仅仅使用mutex将原有的接口包裹起来是不足够的,有些在单线程下感觉良好的接口到了多线程中并不适合,需要重新设计。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值