atoi多线程安全吗?

Is atoi multithread safe?

我在创建多线程程序时遇到了一些错误。使用 gdb 调试时,atoi 函数抛出错误。请帮助,atoi 多线程不安全吗?如果是,有哪些替代方案?

Is atoi multithread safe?

是的,在 atoi() 的 linux 手册页中写道:

┌────────────────────────┬───────────────┬────────────────┐
│Interface               │ Attribute     │ Value          │
├────────────────────────┼───────────────┼────────────────┤
│atoi(), atol(), atoll() │ Thread safety │ MT-Safe locale │
└────────────────────────┴───────────────┴────────────────┘

所以它只是使用您从线程(语言环境)传递的变量,并且是完全线程安全的(MT 安全的),只要您不传递相同的内存位置,例如从两个线程到该函数的指向 char 数组的指针。

如果你这样做,两个函数调用(线程一和线程二)将使用相同的内存位置,在 atoi() 的情况下还不错,因为该函数只从内存中读取,请参阅参数 const char* nptr。它是一个指向常量字符数组的指针。


这里也有对terms/attributes的解释。

MT-安全:

MT-Safe or Thread-Safe functions are safe to call in the presence of other threads. MT, in MT-Safe, stands for Multi Thread.

语言环境:

locale Functions annotated with locale as an MT-Safety issue read from the locale object without any form of synchronization. Functions annotated with locale called concurrently with locale changes may behave in ways that do not correspond to any of the locales active during their execution, but an unpredictable mix thereof.


While using gdb to debug, the atoi function is throwing error.

atoi()函数根本不提供任何错误信息,如果转换不成功它returns 0你不知道那可能是要转换的实际数字。此外,atoi() 函数 而不是 抛出!下面是我用一小部分 C 代码生成的输出,see online at ideone:

atoi with "3"        to integer: +3
atoi with "    3   " to integer: +3
atoi with "   -3   " to integer: -3
atoi with "str 3   " to integer: +0
atoi with "str-3   " to integer: +0
atoi with "    3str" to integer: +3
atoi with "   -3str" to integer: -3
atoi with "str-3str" to integer: +0

你可以看到,如果第一部分是一个数字,忽略第一个数字部分之后的空格和字符,atoi() 转换成功。如果首先有非数字字符,它会失败并且 return 0 并且 not 抛出。


您应该考虑使用 strtol(),因为它可以检测范围溢出,在这种情况下设置 errno
此外,您会得到一个 end pointer,它会显示消耗了多少字符。如果该值为 0,则转换一定有问题。它像 atoi().

一样是线程安全的

我为strtol()做了同样的输出,你也可以在上面的the ideone online example中看到它:

0: strtol with "3"         to integer: +3 | errno =  0, StartPtr = 0x7ffc47e9a140, EndPtr = 0x7ffc47e9a141, PtrDiff = 1
1: strtol with "    3   "  to integer: +3 | errno =  0, StartPtr = 0x7ffc47e9a130, EndPtr = 0x7ffc47e9a135, PtrDiff = 5
2: strtol with "   -3   "  to integer: -3 | errno =  0, StartPtr = 0x7ffc47e9a120, EndPtr = 0x7ffc47e9a125, PtrDiff = 5
3: strtol with "str 3   "  to integer: +0 | errno =  0, StartPtr = 0x7ffc47e9a110, EndPtr = 0x7ffc47e9a110, PtrDiff = 0 --> Error!
4: strtol with "str-3   "  to integer: +0 | errno =  0, StartPtr = 0x7ffc47e9a100, EndPtr = 0x7ffc47e9a100, PtrDiff = 0 --> Error!
5: strtol with "    3str"  to integer: +3 | errno =  0, StartPtr = 0x7ffc47e9a0f0, EndPtr = 0x7ffc47e9a0f5, PtrDiff = 5
6: strtol with "   -3str"  to integer: -3 | errno =  0, StartPtr = 0x7ffc47e9a0e0, EndPtr = 0x7ffc47e9a0e5, PtrDiff = 5
7: strtol with "str-3str"  to integer: +0 | errno =  0, StartPtr = 0x7ffc47e9a0d0, EndPtr = 0x7ffc47e9a0d0, PtrDiff = 0 --> Error!
8: strtol with "s-r-3str"  to integer: +0 | errno =  0, StartPtr = 0x7ffc47e9a0c0, EndPtr = 0x7ffc47e9a0c0, PtrDiff = 0 --> Error!

在此线程中:Detecting strtol failure strtol() 的正确用法讨论了错误检测。

实现 atoi() 的替换非常容易:

int strToInt(const char *text)
{
  int n = 0, sign = 1;
  switch (*text) {
    case '-': sign = -1;
    case '+': ++text;
  }
  for (; isdigit(*text); ++text) n *= 10, n += *text - '0';
  return n * sign;
}

ideone上的演示)

替换已经可用的东西似乎没有多大意义。因此,我想提一些关于这方面的想法。

实施可以根据个人要求进行调整:

  • 可以添加整数溢出检查
  • 可能会返回 text 的最终值(如 strtol())以检查已处理了多少字符或对其他内容进行进一步解析
  • 变体可能用于 unsigned(不接受符号)。
  • 可以接受也可以不接受前面的空格
  • 可以考虑特殊语法
  • 还有其他超出我想象的事情。

将这个想法扩展到其他数字类型,例如floatdouble,就更有趣了

由于浮点数肯定是本地化的主题,因此必须考虑这一点。 (关于十进制整数,我不确定什么可以本地化,但即使是这种情况。)如果实现了具有浮点数语法(如 C 语言)的文本文件 reader,您可能不会忘记调整在使用 strtod() (using setlocale() 之前将语言环境更改为 C)。 (作为德国人,我对这个话题很敏感,因为在德语语言环境中,'.' 和 ',' 的意思与英语中的意思正好相反。)

{ const char *localeOld = setlocale(LC_ALL, "C");
  value = strtod(text);
  setlocale(LC_ALL, localeOld);
}

另一个事实是,语言环境的考虑(即使调整到 C)似乎有点昂贵。几年前,我们实现了自己的浮点数 reader 来替代 strtod() which provided a speed-up of 60 ... 100 in a COLLADA reader(一种 XML 文件格式,其中文件通常提供大量浮点数)。

更新:

受到 Paul Floyd 的反馈的鼓舞,我很好奇 速度 strToInt() 会怎样。因此,我构建了一个简单的测试套件并进行了一些测量:

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int strToInt(const char *text)
{
  int n = 0, sign = 1;
  switch (*text) {
    case '-': sign = -1;
    case '+': ++text;
  }
  for (; isdigit(*text); ++text) n *= 10, n += *text - '0';
  return n * sign;
}

int main(int argc, char **argv)
{
  int n = 10000000; /* default number of measurements */
  /* read command line options */
  if (argc > 1) n = atoi(argv[1]);
  if (n <= 0) return 1; /* ERROR */
  /* build samples */
  assert(sizeof(int) <= 8); /* May be, I want to do it again 20 years ago. */
  /* 24 characters should be capable to hold any decimal for int
   * (upto 64 bit)
   */
  char (*samples)[24] = malloc(n * 24 * sizeof(char));
  if (!samples) {
    printf("ERROR: Cannot allocate samples!\n"
      "(Out of memory.)\n");
    return 1;
  }
  for (int i = 0; i < n; ++i) sprintf(samples[i], "%d", i - (i & 1) * n);
  /* assert correct results, ensure fair caching, pre-heat CPU */
  int *retAToI = malloc(n * sizeof(int));
  if (!retAToI) {
    printf("ERROR: Cannot allocate result array for atoi()!\n"
      "(Out of memory.)\n");
    return 1;
  }
  int *retStrToInt = malloc(n * sizeof(int));
  if (!retStrToInt) {
    printf("ERROR: Cannot allocate result array for strToInt()!\n"
      "(Out of memory.)\n");
    return 1;
  }
  int nErrors = 0;
  for (int i = 0; i < n; ++i) {
    retAToI[i] = atoi(samples[i]); retStrToInt[i] = strToInt(samples[i]);
    if (retAToI[i] != retStrToInt[i]) {
      printf("ERROR: atoi(\"%s\"): %d, strToInt(\"%s\"): %d!\n",
        samples[i], retAToI[i], samples[i], retStrToInt[i]);
      ++nErrors;
    }
  }
  if (nErrors) {
    printf("%d ERRORs found!", nErrors);
    return 2;
  }
  /* do measurements */
  enum { nTries = 10 };
  time_t tTbl[nTries][2];
  for (int i = 0; i < nTries; ++i) {
    printf("Measurement %d:\n", i + 1);
    { time_t t0 = clock();
      for (int i = 0; i < n; ++i) retAToI[i] = atoi(samples[i]);
      tTbl[i][0] = clock() - t0;
    }
    { time_t t0 = clock();
      for (int i = 0; i < n; ++i) retStrToInt[i] = strToInt(samples[i]);
      tTbl[i][1] = clock() - t0;
    }
    /* assert correct results (and prevent that measurement is optimized away) */
    for (int i = 0; i < n; ++i) if (retAToI[i] != retStrToInt[i]) return 3;
  }
  /* report */
  printf("Report:\n");
  printf("%20s|%20s\n", "atoi() ", "strToInt() ");
  printf("--------------------+--------------------\n");
  double tAvg[2] = { 0.0, 0.0 }; const char *sep = "|\n";
  for (int i = 0; i < nTries; ++i) {
    for (int j = 0; j < 2; ++j) {
      double t = (double)tTbl[i][j] / CLOCKS_PER_SEC;
      printf("%19.3f %c", t, sep[j]);
      tAvg[j] += t;
    }
  }
  printf("--------------------+--------------------\n");
  for (int j = 0; j < 2; ++j) printf("%19.3f %c", tAvg[j] / nTries, sep[j]);
  /* done */
  return 0;
}

我在一些平台上试过了。

VS2013 on Windows 10(64 位),发布模式:

Report:
             atoi() |         strToInt()
--------------------+--------------------
              0.232 |              0.200
              0.310 |              0.240
              0.253 |              0.199
              0.231 |              0.201
              0.232 |              0.253
              0.247 |              0.201
              0.238 |              0.201
              0.247 |              0.223
              0.248 |              0.200
              0.249 |              0.200
--------------------+--------------------
              0.249 |              0.212

cygwin 上的 gcc 5.4.0,Windows 10(64 位),gcc -std=c11 -O2

Report:
             atoi() |         strToInt() 
--------------------+--------------------
              0.360 |              0.312 
              0.391 |              0.250 
              0.360 |              0.328 
              0.391 |              0.312 
              0.375 |              0.281 
              0.359 |              0.282 
              0.375 |              0.297 
              0.391 |              0.250 
              0.359 |              0.297 
              0.406 |              0.281 
--------------------+--------------------
              0.377 |              0.289

示例上传并执行于 codingground
gcc 4.8.5 Linux 3.10.0-327.36.3.el7.x86_64, gcc -std=c11 -O2:

Report:
             atoi() |         strToInt() 
--------------------+--------------------
              1.080 |              0.750 
              1.000 |              0.780 
              0.980 |              0.770 
              1.010 |              0.770 
              1.000 |              0.770 
              1.010 |              0.780 
              1.010 |              0.780 
              1.010 |              0.770 
              1.020 |              0.780 
              1.020 |              0.780 
--------------------+--------------------
              1.014 |              0.773 

好吧,strToInt() 稍微快一点。 (没有 -O2,它甚至比 atoi() 慢,但标准库可能也被优化了。)

注:

由于时间测量涉及赋值和循环操作,因此这提供了关于哪个更快的定性说明。它不提供定量因素。 (要得到一个,测量会变得更加复杂。)

由于 atoi() 的简单性,应用程序不得不 非常 经常使用它,直到它变得更值得考虑开发工作...