【问题标题】:Why does POSIX specify wctomb as non-thread-safe, but not mbtowc?为什么 POSIX 将 wctomb 指定为非线程安全的,而不是 mbtowc?
【发布时间】:2011-06-14 16:37:18
【问题描述】:

在 XSH 2.9.1 中,wctomb 被列为不需要线程安全的函数之一。但是,相反的转换函数mbtowc 并没有出现在列表中。在具有使用移位状态的编码的实现中,两者都没有线程安全的 API,并且要求一个是线程安全的而另一个不是,这是没有意义的,而如果不禁止有状态的编码,两者都不能是线程安全的。

wcstombs(在列表中)和mbstowcs(不在列表中)也是如此。由于这两个函数都对以初始移位状态开始和结束的整个字符串进行操作,因此它们不是有状态的,它们的 API 是线程安全的,同样,将一个方向指定为线程安全但不是指定方向是没有意义的其他。

有人能解释一下吗?

【问题讨论】:

  • 在您引用的链接中有一条评论可能会有所启发。 “如果传递 NULL ps 参数,wcrtomb() 和 wcsrtombs() 函数不需要是线程安全的。”我怀疑这可能会让您进一步了解。
  • @David:您正在查看名称中带有r 的函数,这与问题中的函数不同。
  • @R.. 我知道。我的观点是wcrtomb 在传递非NULL ps 时是线程安全的。大概当 ps == NULL 时 wcrtomb 等价于 wctomb。
  • @R.. 进一步推测。 wctomb 不需要是线程安全的,因为如果你想要线程安全,你可以调用 wcrtomb。对于mbtowc,没有r 版本,因此它必须是线程安全的。所有纯粹的猜测都请注意!
  • @R.. 函数通常可以通过使用内部锁实现线程安全

标签: c thread-safety posix multibyte


【解决方案1】:

基本不对称是 ISO C 要求宽字符具有固定宽度(所有字符都相同),并且编码没有移位状态。相比之下,多字节编码取决于语言环境,并且可能具有不同的字符宽度和转换状态。

所有四个函数都在调用之间保持内部状态(mbstowcswcstombs 也必须这样做,因为它们只转换指定数量的字节,而不是在初始移位状态下完成的完整字符串)。

在字符串转换的情况下,内部状态的组成存在细微差别。对于mbstowcs,在一次调用中会转换整数个宽字符。这是因为宽字符具有固定的宽度,并且还因为调用的 n 参数以字符而不是字节指定。相反,对于wcstombsn 参数以字节而不是多字节字符指定。因此,为wcstombs 保留的状态不仅必须包括移位状态,还必须包括部分输出的多字节字符的剩余部分。因为状态是多部分的,所以在没有额外锁定的情况下,对它的操作(加载和存储)在典型架构上不会是原子的。

在这一点上,重要的是要提醒自己“线程安全”在 POSIX 中具有相当的技术含义,即并行调用在逻辑上是可序列化的。这并不意味着并行使用一定非常有用。由于所有四个函数都保持内部状态,很难想象一个调用者(一次)从左到右处理单个线性字符串,但将调用分散到多个线程中。这可以通过在 ISO C 89/90 修正案 1 中引入 mbrtowcwcrtombmbsrtowcswcstombs 来证明,其中 r 标志专门代表“可重入”。

我无法准确解释为什么具有“原子可访问”的内部状态应该更容易使相应的调用更容易以线程安全的方式实现(因为有时必须在单个过程中进行多次访问、加载和存储调用),但可能是因为额外锁定(和重新加载)的负担仅施加在发生实际转移状态的很少访问的代码分支中。

还有一个问题需要解释。并发线程可以调用 setlocale 更改语言环境的字符编码 (LC_CTYPE) 类别。 ISO C 标准规定,这样的操作会导致当前状态(顺便说一下,甚至使用wcrtomb 很好地捕获的状态)变得未定义。这是因为不同语言环境的转换状态可能不会以有用或指定的方式相互映射。虽然这是一个线程场景,有可能打破“可重入”函数系列,但它不一定对正式的线程安全实现构成障碍,因为可以在每次调用期间缓存语言环境设置。

【讨论】:

  • 我看到 wctomb 在避免冗余移位序列时如何在有状态编码中具有内部状态,但 mbtowc 将同样有状态......
  • @R..:你是对的。 mbtowc 实际上也是有状态的。更糟糕的是,mbstowcswcstombs 也好不到哪里去(它们是 n 字符等价物的 1 字符等价物,因此它们也不会以初始状态开始)。如果可以的话,我今晚会做更多的研究并修复我答案的前半部分。这是一个非常有趣的问题!
  • 我的赏金过期了,但如果你确实找到了任何好的理由,我会看看我是否可以打开另一个来分配它。
  • @R..:我将答案的焦点从“转移状态”切换到“部分字符”。我认为它不值得奖励(你仍然可以在接下来的 7 小时内奖励它),但我希望你会发现它是对 POSIX 中不对称性的某种合乎逻辑的解释。
  • +1 这是一个非常有见地的答案,谢谢。我学到了很多。
【解决方案2】:

“启用 Unicode”C 程序的常用方法是在任何地方使用wchar_t 代替char,并调用窄字符标准库函数的宽字符版本。我喜欢这种方法,因为很明显wchar_t* 类型的变量要么指向wchar_t 对象或宽字符串,但char* 类型的变量可以指向char 对象,在本机 char 编码,或数十种支持的字符编码中的任何一种的多字节字符串。 char* 有这么多根本不同的含义,程序员必须非常小心,例如,不要将 UTF-8 编码的多字节字符串传递给需要窄字符串的函数,或者以当前编码传递多字节字符串到一个需要 UTF-8 编码字符串的函数。也许确保mbtowcmbstowcs(将多字节字符串转换为宽字符串的函数)是线程安全的,但不是确保从宽字符串转换为多字节字符串的函数的想法是说服程序员始终保留字符串数据在程序存储器中以宽字符格式存储,其中每个字符恰好代表执行字符集的一个成员,而不是使用可能不同字符编码的窄字符串和多字节字符串的混合。也许标准作者认为这是更有用的方法,或者会更常见。

如果您考虑用 C 编写支持 Unicode 的多线程服务器软件,那么遵循以宽字符串格式保存字符串数据的模式有助于强制区分“从线路”读取的字符串数据和程序内存中的字符串数据。每当收到包含字符串数据有效负载的新消息时,解析该消息及其字符串有效负载的 C 例程可以使用具有多字节到宽字符转换函数的窄字符 I/O 函数将字符串读入程序存储器。如果多个线程解析传入的消息(这是典型的),那么非常希望 mbstowcs 是线程安全的。

【讨论】:

    【解决方案3】:

    正如您在问题中所指出的,wctomb 具有或至少被允许具有(隐藏的)“转换状态”:请参阅 http://pubs.opengroup.org/onlinepubs/009695399/functions/wctomb.html 并将其与 wcrtomb 进行比较:http://pubs.opengroup.org/onlinepubs/009695399/functions/wcrtomb.html 具有明确的“状态”指针。

    POSIX 基本上允许程序员将wctomb 实现为对wcrtomb 的调用,使用static 变量来保持移位状态。如果该变量不是每线程项,则它不是线程安全的。

    (所有这些都非常明显,并且包含在您的问题中,为了清楚起见,我只是在这里重复一遍)

    请注意,wctomb 的任何参数都不会为您提供对隐藏移位状态的任何形式的显式控制(如果有的话)。具体来说,您无法将其重置为启动状态。

    但是现在看看mbtowc: http://pubs.opengroup.org/onlinepubs/009695399/functions/mbtowc.html 那里的文字说:

    对于依赖于状态的编码,此函数通过调用其字符指针参数 s 为空指针而置于其初始状态。使用 s 而非空指针的后续调用将导致函数的内部状态根据需要进行更改。如果编码具有状态依赖性,则以 s 作为空指针的调用将导致此函数返回非零值,否则返回 0。如果实现使用特殊字节来改变移位状态,这些字节不应产生单独的宽字符代码,而应与相邻字符组合。

    也就是说,它们为您提供了一种检测和控制隐藏状态(如果存在)的显式方法。因此,即使它存在并且不是特定于线程的,您也可以照原样“进行自己的控制”。

    虽然我不能发誓,但我认为这就是mbtowc 未被列为非线程安全的原因。

    (不过,这不是我在标准中编写文本的方式!再说一次,如果我按照我的方式,很多这些功能甚至都不存在。:-))

    【讨论】:

      【解决方案4】:

      我认为这只是基于这样一个假设,即使用一组宽字符编码数据,由于代码点的固定宽度,程序员可以预测每个线程需要分配/释放多少内存。但是当朝着另一个方向前进时,根据编码,可能无法“预测”需要提前分配多少内存,从而为错误创造了更大的空间。

      更新:在找到标准的旧版本后,我注意到man page for the wctomb() function 中的措辞有所不同:“wctomb() 函数不需要可重入。不需要的函数重入不需要是线程安全的。”我认为这暗示了标准中的另一个隐含假设:mbtowc() 是或应该是可重入的......

      【讨论】:

      • MB_CUR_MAXMB_LEN_MAX 是为此目的而存在的,但这与线程安全无关......
      猜你喜欢
      • 1970-01-01
      • 2012-11-20
      • 2013-10-11
      • 2020-10-10
      • 1970-01-01
      • 2012-09-19
      • 2010-12-10
      相关资源
      最近更新 更多