【问题标题】:boost::format() appears to not be thread safe because std::ctype<char>::narrow() is not thread safeboost::format() 似乎不是线程安全的,因为 std::ctype<char>::narrow() 不是线程安全的
【发布时间】:2019-01-05 01:59:43
【问题描述】:

我遇到了在多个线程中使用 boost::format() 的问题。 boost 格式库使用 boost 解析库,它使用 /usr/include/c++/4.8/bits/locale_facets.h 中定义的函数 std::ctype::narrow() (我使用的是 G++ 版本 4.8)。

narrow() 函数不是很无害。实例变量 _M_narrow 是一个缓存。我发现跨线程同时写入和读取此缓存。必须锁定线程才能使用 boost::format 似乎很愚蠢,以至于避免使用 boost::format 这让我觉得我一定错过了一些东西。有没有人对这个问题有更深入的了解?

  /**
   *  @brief  Narrow char
   *
   *  This function converts the char to char using the simplest
   *  reasonable transformation.  If the conversion fails, dfault is
   *  returned instead.  For an underived ctype<char> facet, @a c
   *  will be returned unchanged.
   *
   *  This function works as if it returns ctype<char>::do_narrow(c).
   *  do_narrow() must always return the same result for the same input.
   *
   *  Note: this is not what you want for codepage conversions.  See
   *  codecvt for that.
   *
   *  @param __c  The char to convert.
   *  @param __dfault  Char to return if conversion fails.
   *  @return  The converted character.
  */
  char
  narrow(char_type __c, char __dfault) const
  {
    if (_M_narrow[static_cast<unsigned char>(__c)])
      return _M_narrow[static_cast<unsigned char>(__c)];
    const char __t = do_narrow(__c, __dfault);
    if (__t != __dfault)
      _M_narrow[static_cast<unsigned char>(__c)] = __t;
    return __t;
  }

【问题讨论】:

    标签: c++ multithreading boost


    【解决方案1】:

    _M_narrow 的定义是:

    mutable char _M_narrow[1 + static_cast<unsigned char>(-1)];
    

    实际上,并行写入/读取通常是一种危险,但在这种特殊情况下,据我所知,这很好。 (如果我错了,请有人纠正我)

    • 数据结构是一个数组,如果写入它不会在结构上失效(与例如 std::map 相对)
    • 元素本身是char。编写char 是所有主要处理器上的原子操作(与intlongdouble 相对)
    • 我看到的唯一竞争条件是在检查缓存是否为空的if 和对缓存的写操作之间。但是如果发生竞争,两个线程并行评估do_narrow,然后都将相同的结果写入缓存。

    我认为这涵盖了所有危险情况,这意味着此操作实际上是线程安全的,不需要锁定。

    这种实现类型的唯一问题是 clang 的线程清理器讨厌它并且总是将其报告为潜在危险,即使它是安全的。

    【讨论】:

    • 假设你没有架构保证char上的赋值/读取的原子性,两个线程写入同一个字符是不安全的
    • 我不确定你在说什么......在我所知道的所有平台上,x86、x86_64、arm 读/写一个 char 总是保证是原子的。当然,读取-计算-写入操作不是原子的,如“i++”,但单次读取或写入是原子的。保持处理器内核和高速缓存之间的内存一致是 x86 处理器的工作,程序员无需担心。如果您不同意,可以给我链接此信息的来源吗?
    • 有关缓存一致性的更详细讨论,请阅读此主题:stackoverflow.com/questions/558848/…
    • 实际上你是对的。 char 是原子的。我不知道 C++ 标准是否说明了 char 的原子性。这也许就是为什么有 std::atomic_char 和 char 的原因。 C++ 中的基本类型不保证是原子的stackoverflow.com/a/35226186/3951920
    • 你当然是对的,我的错。将字符数组替换为std::atomic&lt;char&gt; 数组是明智的。在字符已经是原子的所有平台上,这将是零开销,很可能只会在内部添加一个 mem_fence。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-14
    • 1970-01-01
    • 2015-08-05
    • 1970-01-01
    • 1970-01-01
    • 2018-03-31
    相关资源
    最近更新 更多