免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: wlmqgzm
打印 上一主题 下一主题

[C++] 读性能超过Memcached 65%, 单核也超过redis, 支持日志支持掉电保护,欢迎试用 [复制链接]

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
101 [报告]
发表于 2017-02-10 22:23 |只看该作者
本帖最后由 yulihua49 于 2017-02-10 22:25 编辑
wlmqgzm 发表于 2017-02-10 19:28
关于日志的多个写请求的写合并技术介绍如下:

1)不保存日志的情况下,在低端4核CPU下,可以实现24万TPS ...

ORACLE我实现了每秒插入35000个大记录,160个列的。数据库是4核8G内存的虚拟机。
你也就是他的5倍速度。

论坛徽章:
9
程序设计版块每日发帖之星
日期:2015-10-18 06:20:00程序设计版块每日发帖之星
日期:2015-11-01 06:20:00程序设计版块每日发帖之星
日期:2015-11-02 06:20:00每日论坛发贴之星
日期:2015-11-02 06:20:00程序设计版块每日发帖之星
日期:2015-11-03 06:20:00程序设计版块每日发帖之星
日期:2015-11-04 06:20:00程序设计版块每日发帖之星
日期:2015-11-06 06:20:00数据库技术版块每周发帖之星
日期:2015-12-02 15:02:47数据库技术版块每日发帖之星
日期:2015-12-08 06:20:00
102 [报告]
发表于 2017-02-11 00:23 |只看该作者
本帖最后由 wlmqgzm 于 2017-02-11 00:39 编辑

回复 102# yulihua49

我的测试KV数据大小是4.1KByte, 也不算小了.

性能上比较突出的主要是在每条插入/更新记录都刷新写日志的情况下, 由于程序特殊的设计, 比常规数据库有更高的处理log日志的能力, 仍然可以有16万TPS的处理性能, 是常规数据库的X倍性能.

另外其实日志的代码还有更多的优化, 日志自动合并功能, 例如:对于1000个连接在瞬间修改了同一条记录数据(类似淘宝秒杀的环节), 等待返回结果, 只要相关日志还没有保存, 还在处理队列中, 在日志层有自动合并机制, 最终只写入最后的结果,只记录了一条日志记录,然后再统一给1000个连接返回执行成功. 这些功能都是传统数据库不具备的功能点. 总之, 连接数越多, 数据冲突越大,性能越高.

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
103 [报告]
发表于 2017-02-11 09:11 |只看该作者
本帖最后由 yulihua49 于 2017-02-11 09:16 编辑
wlmqgzm 发表于 2017-02-11 00:23
回复 102# yulihua49

我的测试KV数据大小是4.1KByte, 也不算小了.

我是批量操作,一批1000行,写3个日志。而且是16个线程并行操作。支持并行插入/修改,对你来说确实有难度。而且,ORACLE并行插入/修改居然能够正确处理重复键问题。
建议你也提供批量功能,批量查询,批量插入-修改。这样可以达到100倍啦。

论坛徽章:
9
程序设计版块每日发帖之星
日期:2015-10-18 06:20:00程序设计版块每日发帖之星
日期:2015-11-01 06:20:00程序设计版块每日发帖之星
日期:2015-11-02 06:20:00每日论坛发贴之星
日期:2015-11-02 06:20:00程序设计版块每日发帖之星
日期:2015-11-03 06:20:00程序设计版块每日发帖之星
日期:2015-11-04 06:20:00程序设计版块每日发帖之星
日期:2015-11-06 06:20:00数据库技术版块每周发帖之星
日期:2015-12-02 15:02:47数据库技术版块每日发帖之星
日期:2015-12-08 06:20:00
104 [报告]
发表于 2017-02-12 18:18 |只看该作者
本帖最后由 wlmqgzm 于 2017-02-12 18:37 编辑

上点干货,专门谈一下网络层的健壮性, 以及自己的经验教训。

以前想把程序中的网络层尝试优化一下, 以便提供更高的性能, 主要的优化思路是:考虑使用异步和同步混合模式,以及一些参数更改。

1)读数据之前, 先看一下 读缓冲区中有多少数据可以用于读取,如果已经有数据可以读取,那么就直接read_some读取,用同步的方式读取, 如果没有数据可读, 则使用异步读取注册call_back函数。
这样在有数据可读的情况下, 减少一次读函数入任务队列和出任务队列的开销。

2)写数据时, 先尝试write_some同步写入, 如果能够写入全部, 则不用注册异步写入的callback了, 写入部分则将字符串截断为substr, 只保留没有写入的部分,再注册异步写入的callback。
这样在写缓冲区有空闲空间的情况下, 减少一次写函数入任务队列和出任务队列的开销。

3)在写数据前检查 写入的数据是否是一组string,如果是一组string, 则先设置no_delay为false, 意思是delay延迟发送, 这样可以尽量合并string的内容为更大的IP包,提高IP包的效率,当全部strings都写入缓冲区完成后,再设置no_delay为true, 意思是立即发送,不要延迟等待。单string默认就是no_delay为true, 不需要修改。

功能上测试之类的都没有问题,也确实提高了性能。但是,在进行健壮性测试的时候, 发现如果在非常高的并发性,数万客户端不按标准来,正在收发数据中突然乱发RST拆线,这个时候,程序将会崩溃。然后,为了提高健壮性,在所有的read/write操作之前,添加了额外的底层代码,检查是否处于拆线状态,部分解决了问题。
在进行长时间类似的非标TCP/IP协议乱序冲击测试下,发现无论增加检测代码,多线程同步模式都有可能会程序崩溃,单线程同步模式抗住了冲击,多线程异步模式+保护性检测代码 也抗住了冲击,健壮性可以得到保证。
Boost::Asio::set_option无法抗住频繁大量的RST, 偶然会出现set_option时,socket已经拆线,set_option导致程序崩溃的问题。

所以,最终得出3点提高健壮性的结论:
1)如果采用Boost:Asio同步读写,建议单线程方式使用。
2)Boost::Asio 多线程异步模式的健壮性, 在增加额外的检测代码后,健壮性也是可以有保证的。
3)程序读写的时候, 尽量不要set_option。
最终我们使用的方案是:多线程异步模式+保护性检测代码 ,这样性能和稳定性都有保证。

放一段代码,功能是: 看一下 读缓冲区中有多少数据可以用于读取。
注意:我们现在实际工作中不使用这部分代码,虽然加上了一段检测代码,但是依旧有非常小的概率,下列代码会崩溃。
size_t  Asio_tcp_socket::socket_read_available( const std::shared_ptr<Struct_socket_data> &sp_socket_data )
{
  boost::system::error_code  error_code1;
  boost::asio::ip::tcp::socket &socket1 = sp_socket_data->socket1;

  //  必须要在  socket1.available 之前做此检查,  如果对方RST, 那么  socket1.available 会导致崩溃。
  if( find_socket_closed( socket1, sp_socket_data->int_socket_handle_init ) )  return  0;  //   如果已经关闭

  size_t  size_available = socket1.available( error_code1 );
  if( error_code1 ) {  // 如果检测到错误,
    int ec_value =  error_code1.value();
    std::string str_ec_category = error_code1.category().name();
    if( g_uchar_message_out_level>=3 )  {
      std::string str_msg = "available error. ";
      str_msg += sp_socket_data->str_connect_msg;
      str_msg += ", error_category=";
      str_msg += str_ec_category;
      str_msg +=  ", error value=";
      char  chars_tmp[24];
      str_msg += int_to_chars( ec_value, chars_tmp );
      str_msg +=  ", error=";
      str_msg += error_code1.message();
      log_message_str( str_msg );
      }
    return  0;
    }
  return  size_available;
}

论坛徽章:
9
程序设计版块每日发帖之星
日期:2015-10-18 06:20:00程序设计版块每日发帖之星
日期:2015-11-01 06:20:00程序设计版块每日发帖之星
日期:2015-11-02 06:20:00每日论坛发贴之星
日期:2015-11-02 06:20:00程序设计版块每日发帖之星
日期:2015-11-03 06:20:00程序设计版块每日发帖之星
日期:2015-11-04 06:20:00程序设计版块每日发帖之星
日期:2015-11-06 06:20:00数据库技术版块每周发帖之星
日期:2015-12-02 15:02:47数据库技术版块每日发帖之星
日期:2015-12-08 06:20:00
105 [报告]
发表于 2017-02-12 21:39 |只看该作者
本帖最后由 wlmqgzm 于 2017-02-12 21:52 编辑

再上点干货, 谈一下常见的 spin_lock 的性能上挖的坑,  

很多范例里面的spin_lock只是大学生的作业,只能作为作业, 真的不能使用,一旦使用,效率可能更低。

先展示一个低效率的 spin_lock  

class spinlock {
    private:
        typedef enum {Locked, Unlocked} LockState;
        std::atomic<LockState> state_;
    public:
        /// Constructor
        spinlock() noexcept : state_(Unlocked) {}
  
        /// Blocks until a lock can be obtained for the current execution agent.


        void lock() noexcept {

//  这里第1行exchange是写模式,每次都要独占cache,并且将其他CPU的cache设置为invalide,
  //  如果两个以上CPU在等待这把锁,将交替将64字节内存反复同步,整个内存总线被无效的数据读写占用,
  //  数据冲突激烈,影响其他线程的内存操作,降低整体系统的性能
//  在系统总线上会产生大量的traffic,开销是非常大的,而且unlock的CPU还必须同其它正在竞争spinlock的CPU去竞争cacheline ownership. 随着CPU数目的增多,性能会衰减的非常快。

            while (state_.exchange(Locked, std::memory_order_acquire) == Locked) {
                /* busy-wait */
            }
        }

        /// Releases the lock held by the execution agent.
        void unlock() noexcept {
            state_.store(Unlocked, std::memory_order_release);
        }
    };

所以,spin_lock 每家公司, 每个牛B的产品 都有自己的写法,都比上面这个复杂得多,才能真正比 std::mutex 快,  很多人随便抄一个简单的 spin_lock 拿来用,都是在挖性能的坑。。。。。。。。。。。。

论坛徽章:
9
程序设计版块每日发帖之星
日期:2015-10-18 06:20:00程序设计版块每日发帖之星
日期:2015-11-01 06:20:00程序设计版块每日发帖之星
日期:2015-11-02 06:20:00每日论坛发贴之星
日期:2015-11-02 06:20:00程序设计版块每日发帖之星
日期:2015-11-03 06:20:00程序设计版块每日发帖之星
日期:2015-11-04 06:20:00程序设计版块每日发帖之星
日期:2015-11-06 06:20:00数据库技术版块每周发帖之星
日期:2015-12-02 15:02:47数据库技术版块每日发帖之星
日期:2015-12-08 06:20:00
106 [报告]
发表于 2017-02-12 21:56 |只看该作者
本帖最后由 wlmqgzm 于 2017-02-12 21:58 编辑

更高效率的 spin_lock

void Spin_lock::lock( void )
{
  //  这里第1行是写模式,每次都要独占cache,并且将其他CPU的cache设置为invalide,
  //  如果两个以上CPU在等待这把锁,将交替将64字节内存反复同步, 数据冲突激烈,影响其他线程的内存操作,降低整体性能
  while( d_atomic_bool.exchange( true, std::memory_order_acquire ) )  {
    //  下面这条指令是只读模式检查,无数据冲突
    while( d_atomic_bool.load( std::memory_order_relaxed ) )  {
      yield();
      continue;
      }
    continue;
    }
  //  上锁成功
  return;
}


bool Spin_lock::try_lock( void )
{
  return  !d_atomic_bool.exchange( true, std::memory_order_acquire );
}


void Spin_lock::unlock( void )
{
  d_atomic_bool.store( false, std::memory_order_release );  // 设置为false
  return;
}

论坛徽章:
9
程序设计版块每日发帖之星
日期:2015-10-18 06:20:00程序设计版块每日发帖之星
日期:2015-11-01 06:20:00程序设计版块每日发帖之星
日期:2015-11-02 06:20:00每日论坛发贴之星
日期:2015-11-02 06:20:00程序设计版块每日发帖之星
日期:2015-11-03 06:20:00程序设计版块每日发帖之星
日期:2015-11-04 06:20:00程序设计版块每日发帖之星
日期:2015-11-06 06:20:00数据库技术版块每周发帖之星
日期:2015-12-02 15:02:47数据库技术版块每日发帖之星
日期:2015-12-08 06:20:00
107 [报告]
发表于 2017-02-12 22:07 |只看该作者
补充一点背景资料:
关于  d_atomic_bool.exchange( true, std::memory_order_acquire );

不使用CAS(compare-and-swap)的实现:  

前面的CAS实现中lock函数中的原子操作需要3个步骤:

    取flag的值;
    比较flag的值;
    置flag一个新值。

由于自旋锁只有两个状态,事实上获取自旋锁可以使用下面的流程


其中灰色矩形中的2步应是一个不可分割的原子操作:

    取flag的值;
    置flag一个新值。

这样就将原子操作中需要做的3步减少到2步了。

论坛徽章:
44
15-16赛季CBA联赛之浙江
日期:2021-10-11 02:03:59程序设计版块每日发帖之星
日期:2016-07-02 06:20:0015-16赛季CBA联赛之新疆
日期:2016-04-25 10:55:452016科比退役纪念章
日期:2016-04-23 00:51:2315-16赛季CBA联赛之山东
日期:2016-04-17 12:00:2815-16赛季CBA联赛之福建
日期:2016-04-12 15:21:2915-16赛季CBA联赛之辽宁
日期:2016-03-24 21:38:2715-16赛季CBA联赛之福建
日期:2016-03-18 12:13:4015-16赛季CBA联赛之佛山
日期:2016-02-05 00:55:2015-16赛季CBA联赛之佛山
日期:2016-02-04 21:11:3615-16赛季CBA联赛之天津
日期:2016-11-02 00:33:1215-16赛季CBA联赛之浙江
日期:2017-01-13 01:31:49
108 [报告]
发表于 2017-02-12 23:15 |只看该作者
回复 108# wlmqgzm

CAS的意思就是Compare And Set,也就是说比较和设置是一步。拆开的话就会有race condition了。

论坛徽章:
9
程序设计版块每日发帖之星
日期:2015-10-18 06:20:00程序设计版块每日发帖之星
日期:2015-11-01 06:20:00程序设计版块每日发帖之星
日期:2015-11-02 06:20:00每日论坛发贴之星
日期:2015-11-02 06:20:00程序设计版块每日发帖之星
日期:2015-11-03 06:20:00程序设计版块每日发帖之星
日期:2015-11-04 06:20:00程序设计版块每日发帖之星
日期:2015-11-06 06:20:00数据库技术版块每周发帖之星
日期:2015-12-02 15:02:47数据库技术版块每日发帖之星
日期:2015-12-08 06:20:00
109 [报告]
发表于 2017-02-13 02:04 |只看该作者
本帖最后由 wlmqgzm 于 2017-02-13 15:21 编辑

再补充一下  yield() 函数的定义 ,
{
      _mm_pause();   //  这个大约是等待40个时钟周期, 大约12个纳秒, 相当于asm("pause")指令, 对于Intel超线程CPU, 可以实现将物理CPU执行切换到另外一个逻辑CPU, 另外可以减少内存冲突

    std::this_thread::yield();  
//  这个是一个可选的设计,有争议, 我们的程序考虑有可能还是等待比较长的时间(可以替换掉std::mutex),并且应用线程数+网络层线程数>CPU数量,考虑引入此函数解决spin_lock效率问题,
// 当CPU任务队列中有其他可执行线程时, 将进行线程切换,线程切换时间大约是10微秒
//  此函数在无任务可执行时,我们测试平均是113纳秒延迟。

//  可以替代的选项是usleep(X)或者类似的代码,我们测试在我们的linux操作系统中usleep(0)的延迟时间大约是50微秒, 这个延迟时间比较长。
//  总之, yield()的设计比较考验设计工程师的总体架构把握能力,
}

//  下面是Boost库的yield的设计,K每次循环加1,
//   BOOST_SMT_PAUSE   就是 asm("pause")指令
//  sched_yield(); 等效于   std::this_thread::yield();  
//  nanosleep( &rqtp, 0 ); 等效于 usleep(1) ,实际测试也是延迟了大约50微秒
//  从策略上讲,Boost库的设计是依次增加时延,最后增加到50微秒, 这个设计思路与 ngix _spin_lock的设计很接近, 可以参考, 但是, 我们认为引入nanosleep导致的问题很严重, 我们的很多应用只需要占用2-4微秒的锁时间, nanosleep却有50微秒以上的延迟时间, 这个延迟时间的代价是我们不能接收的, 所以, 根据综合考虑, 我们推荐yield()做的非常简单, _mm_pause();    std::this_thread::yield();  

inline void yield( unsigned k )
{
    if( k < 4 )
    {
    }
#if defined( BOOST_SMT_PAUSE )
    else if( k < 16 )
    {
        BOOST_SMT_PAUSE
    }
#endif
    else if( k < 32 || k & 1 )
    {
        sched_yield();
    }
    else
    {
        // g++ -Wextra warns on {} or {0}
        struct timespec rqtp = { 0, 0 };

        // POSIX says that timespec has tv_sec and tv_nsec
        // But it doesn't guarantee order or placement

        rqtp.tv_sec = 0;
        rqtp.tv_nsec = 1000;

        nanosleep( &rqtp, 0 );
    }
}






  4, Pause指令解释(from intel):
  Description
  Improves the performance of spin-wait loops. When executing a “spin-wait loop,” a Pentium 4 or Intel Xeon processor suffers a severe performance penalty when exiting the loop because it detects a possible memory order violation. The PAUSE instruction provides a hint to the processor that the code sequence is a spin-wait loop. The processor uses this hint to avoid the memory order violation in most situations, which greatly improves processor performance. For this reason, it is recommended that a PAUSE instruction be placed in all spin-wait loops.
  提升spin-wait-loop的性能,当执行spin-wait循环的时候,笨死和小强处理器会因为在退出循环的时候检测到memory order violation而导致严重的性能损失,pause指令就相当于提示处理器哥目前处于spin-wait中。在绝大多数情况下,处理器根据这个提示来避免violation,藉此大幅提高性能,由于这个原因,我们建议在spin-wait中加上一个pause指令。
  名词解释(以下为本人猜想):memory order violation,直译为-内存访问顺序冲突,当处理器在(out of order)乱序执行的流水线上去内存load某个内存地址的值(此处是lock)的时候,发现这个值正在被store,而且store本身就在load之前,对于处理器来说,这就是一个hazard,流水流不起来。
  在本文中,具体是指当一个获得锁的工作线程W从临界区退出,在调用unlock释放锁的时候,有若干个等待线程S都在自旋检测锁是否可用,此时W线程会产生一个store指令,若干个S线程会产生很多load指令,在store之后的load指令要等待store在流水线上执行完毕才能执行,由于处理器是乱序执行,在没有store指令之前,处理器对多个没有依赖的load是可以随机乱序执行的,当有了store指令之后,需要reorder重新排序执行,此时会严重影响处理器性能,按照intel的说法,会带来25倍的性能损失。Pause指令的作用就是减少并行load的数量,从而减少reorder时所耗时间。
  An additional function of the PAUSE instruction is to reduce the power consumed by a Pentium 4 processor while executing a spin loop. The Pentium 4 processor can execute a spin-wait loop extremely quickly, causing the processor to consume a lot of power while it waits for the resource it is spinning on to become available. Inserting a pause instruction in a spin-wait loop greatly reduces the processor’s power consumption.
  Pause指令还有一个附加作用是减少笨死处理器在执行spin loop时的耗电量。当笨死探测锁是否可用时,笨死以飞快的速度执行spin-wait loop,导致消耗大量电量。在spin-wait loop中插入一条pause指令会显著减少处理器耗电量。
  This instruction was introduced in the Pentium 4 processors, but is backward compat­ible with all IA-32 processors. In earlier IA-32 processors, the PAUSE instruction operates like a NOP instruction. The Pentium 4 and Intel Xeon processors implement the PAUSE instruction as a pre-defined delay. The delay is finite and can be zero for some processors. This instruction does not change the architectural state of the processor (that is, it performs essentially a delaying no-op operation).
  该指令在笨死中被引入,但是向后兼容所有IA-32处理器,在早期的IA-32处理器中,pause指令以nop的方式来运行,笨死和小强以一个预定义delay的方式来实现pause,该延迟是有限的,在某些处理器上可能是0,pause指令并不改变处理器的架构(位?)状态(也就是说,它是一个延迟执行的nop指令)。至于Pause指令的延迟有多大,intel并没有给出具体数值,但是据某篇文章给出的测试结果,大概有38~40个clock左右(官方数字:nop延迟是1个clock),一下子延迟40个clock,intel也够狠的。
  This instruction’s operation is the same in non-64-bit modes and 64-bit mode.
  该指令在64位与非64位模式下的表现是一致的。

论坛徽章:
9
程序设计版块每日发帖之星
日期:2015-10-18 06:20:00程序设计版块每日发帖之星
日期:2015-11-01 06:20:00程序设计版块每日发帖之星
日期:2015-11-02 06:20:00每日论坛发贴之星
日期:2015-11-02 06:20:00程序设计版块每日发帖之星
日期:2015-11-03 06:20:00程序设计版块每日发帖之星
日期:2015-11-04 06:20:00程序设计版块每日发帖之星
日期:2015-11-06 06:20:00数据库技术版块每周发帖之星
日期:2015-12-02 15:02:47数据库技术版块每日发帖之星
日期:2015-12-08 06:20:00
110 [报告]
发表于 2017-02-13 02:36 |只看该作者
本帖最后由 wlmqgzm 于 2017-02-13 02:56 编辑
windoze 发表于 2017-02-12 23:15
回复 108# wlmqgzm

CAS的意思就是Compare And Set,也就是说比较和设置是一步。拆开的话就会有race con ...

不存在race condition,  详细解释如下:  
不管内部循环是什么代码, 由于最终起作用的指令还是第1条指令, d_atomic_bool.exchange( true, std::memory_order_acquire ),  因此不存在race condition.


//  这里第1行 d_atomic_bool.load( std::memory_order_relaxed  是写模式,每次都要独占cache,并且将其他CPU的cache设置为invalide, 第1行改为 cas 指令也一样有内存写预占争用.

  //  如果两个以上CPU在等待这把锁,将交替将64字节内存反复同步, 数据冲突激烈,影响其他线程的内存操作,降低整体性能, 是影响的全部CPU的工作效率, 因为产生了内存争用, 产生了总线争用
  while( d_atomic_bool.exchange( true, std::memory_order_acquire ) )  {

    //  下面的指令主要是为了减少CAS指令循环对内存总线带来的不良影响, 尤其是目前CPU核心数量越来越多的情况, 下列循环只有在锁被释放后, 才会跳出循环, 但是这个指令消耗的资源要极少,
    //  很多人没有认识到CAS指令的代价高得惊人, 那么, 为了将代价减少到极致, 我们就增加了下面这组指令来解决问题,

    //  增加下面这组指令是 只读模式检查, 只需要锁没有被释放, 这里继续循环, 但是这组spin是没有竞争状态的, 不会对内存和cache产生任何影响, 只要锁不释放, 永远不会内存总线的争夺, 因此提高了整体的效率
    while( d_atomic_bool.load( std::memory_order_relaxed ) )  {
      yield();
      continue;
      }

    //  当 d_atomic_bool 的锁被释放后,  执行存在内存冲突的CAS指令/exchange指令, 这样在多数情况下, 就不存在多个CPU因为CAS指令造成内存总线长期被无效内容占用, 整体CPU效率大幅度下滑的情况出现.   
    //  不管内部循环是什么代码, 由于最终起作用的指令还是第1条指令, d_atomic_bool.exchange( true, std::memory_order_acquire ),  因此不存在race condition.
   
    continue;
    }


另外, 你的开源软件中spin_lock也是用 exchange , 因为这个指令比cas效率高, cas指令也是要预占读写权限的, 与exchange没有区别,

//
//  spinlock.hpp
//  fibio
//
//  Created by Chen Xu on 14-6-20.
//  Copyright (c) 2014 0d0a.com. All rights reserved.
//

#ifndef fibio_fibers_detail_spinlock_hpp
#define fibio_fibers_detail_spinlock_hpp

#include <atomic>

namespace fibio { namespace fibers { namespace detail {
    /**
     * class spinlock
     *
     * A spinlock meets C++11 BasicLockable concept
     */
    class spinlock {
    private:
        typedef enum {Locked, Unlocked} LockState;
        std::atomic<LockState> state_;

    public:
        /// Constructor
        spinlock() noexcept : state_(Unlocked) {}
  
        /// Blocks until a lock can be obtained for the current execution agent.
        void lock() noexcept {
            while (state_.exchange(Locked, std::memory_order_acquire) == Locked) {
                /* busy-wait */
            }
        }

        /// Releases the lock held by the execution agent.
        void unlock() noexcept {
            state_.store(Unlocked, std::memory_order_release);
        }
    };
}}} // End of namespace fibio::fibers::detail

#endif

关于锁, 前一段时间研究比较多, 在公司里重写基本库, 基本上把各类锁都用最新的思路重新写了一遍, 看的资料也比较多, 核心概念上不会犯错误的.   


您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP