【问题标题】:Is atoi multithread safe? [closed]atoi 多线程安全吗? [关闭]
【发布时间】:2017-11-17 16:24:36
【问题描述】:

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

【问题讨论】:

  • 当你说“抛出错误”时,你是什么意思?您是否可能传递了一个无效的指针?您能否尝试创建一个Minimal, Complete, and Verifiable Example 并展示给我们看?请编辑您的问题以包含代码以及更多详细信息。
  • atoi 导致超出范围输入的未定义行为,最好不要使用它
  • 是的,它是多线程安全的。但是,您的问题中没有任何信息可以用来帮助您解决问题。
  • 为了帮助你理解反对意见,这个问题有一种“我有一个问题,我不会详细描述。这可能是我没有证据支持它的疯狂理论任何?”感觉到它。什么错误?代码是什么样的?什么平台?

标签: c multithreading runtime-error atoi


【解决方案1】:

atoi 多线程安全吗?

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

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

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

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


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

MT 安全:

MT-Safe 或 Thread-Safe 函数在存在其他线程的情况下可以安全调用。 MT,在 MT-Safe 中,代表多线程。

地区:

使用语言环境注释的语言环境函数作为 MT 安全问题读取 来自 locale 对象,没有任何形式的同步。 用语言环境注释的函数同时调用 语言环境更改的行为方式可能与 任何在执行期间处于活动状态的语言环境,但 不可预知的混合。


使用gdb调试时,atoi函数报错。

atoi() 函数根本不提供任何错误信息,如果转换不成功,它会返回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() 的正确用法。

【讨论】:

    【解决方案2】:

    替换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 中),您可能不会忘记将语言环境调整为 @ 987654335@ 之前使用strtod()(使用setlocale())。 (作为德国人,我对这个话题很敏感,因为在德语语言环境中,'.' 和 ',' 的意思就像英语中的反之亦然。)

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

    另一个事实是,考虑语言环境(即使调整为 C)似乎有点昂贵。几年前,我们实现了自己的浮点阅读器来替代strtod(),它在COLLADA 阅读器中提供了60 ... 100 的加速(文件通常提供大量浮点数的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;
    }
    

    我在一些平台上试过这个。

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

    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上上传并执行的示例
    Linux 3.10.0-327.36.3.el7.x86_64 上的 gcc 4.8.5,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() 的简单性,应用程序不得不非常经常使用它,直到它变得值得考虑开发工作...

    【讨论】:

    • glibc atoi 只是调用 strtol,所以性能方面,自定义版本可能会快很多。
    • @PaulFloyd 出于好奇,我对atoi()strToInt() 做了一个简单的比较。 “快了很多”可能是相当热情的。它...只是更快。
    • 好的,谢谢,很有趣。
    • @PaulFloyd 是的,我需要一些时间才能获得关于strToInt() 的安全运行时间的“投资回报”,而我需要对样本进行编程以找出这一点。 ..
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-15
    • 1970-01-01
    相关资源
    最近更新 更多