【问题标题】:C++11 function static variable initialization weirdnessC ++ 11函数静态变量初始化怪异
【发布时间】:2016-03-21 11:50:23
【问题描述】:

我有一个简单的函数,可以将一些 HTTP 状态代码映射到字符串:

const std::string & status_name(int code)
{
    static std::unordered_map<int, std::string> status_table {
        { 0, "UNKNOWN" },
        { 100, "Continue" },
        { 101, "Switching Protocols" },
        { 200, "OK" },
        { 201, "Created" },
        { 202, "Accepted" },
        { 203, "Non-Authoritative Information" },
        { 204, "No Content" },
        { 205, "Reset Content" },
        { 206, "Partial Content" },
        { 300, "Multiple Choices" },
        { 301, "Moved Permanently" },
        { 302, "Found" },
        { 303, "See Other" },
        { 304, "Not Modified" },
        { 305, "Use Proxy" },
        { 307, "Temporary Redirect" },
        { 400, "Bad Request" },
        { 401, "Unauthorized" },
        { 402, "Payment Required" },
        { 403, "Forbidden" },
        { 404, "Not Found" },
        { 405, "Method Not Allowed" },
        { 406, "Not Acceptable" },
        { 407, "Proxy Authentication Required" },
        { 408, "Request Timeout" },
        { 409, "Conflict" },
        { 410, "Gone" },
        { 411, "Length Required" },
        { 412, "Precondition Failed" },
        { 413, "Request Entity Too Large" },
        { 414, "Request-URI Too Long" },
        { 415, "Unsupported Media Type" },
        { 416, "Requested Range Not Satisfiable" },
        { 417, "Expectation Failed" },
        { 499, "Rotten session" },
        { 500, "Internal Server Error" },
        { 501, "Not Implemented" },
        { 502, "Bad Gateway" },
        { 503, "Service Unavailable" },
        { 504, "Gateway Timeout" },
        { 505, "HTTP Version Not Supported" },
    };

    auto it = status_table.find(code);

    if (it != status_table.end())
        return it->second;

    return status_table.at(0);
}

有一天,在我们的测试服务器上发生了崩溃。分析核心转储似乎是在status_table.at(0); 中引发了std::out_of_range 异常。另一个奇怪的事情是有问题的状态代码是200。崩溃的程序滥用了多个线程,所以我的胃告诉我这个问题与线程有关。

我真的很困惑这怎么可能。它是C++11,这个版本的标准提供了关于静态变量初始化的线程安全的保证。编译器是clang-3.3,架构是x64,CPU是Intel(R) Xeon(R) CPU E5506

我想到的只有两件事:

  1. 代码中有一些错误,我不想看到;
  2. 这种怪异与 CPU 缓存不一致有关:导致崩溃的线程将保护变量视为指示“已初始化”状态 status_table 但对象本身是空的。

问题更新:

在制作了一个小测试用例后,我能够轻松地重现问题:

#include <thread>
#include <atomic>
#include <string>
#include <unordered_map>
#include <vector>
#include <memory>
#include <iostream>
#include <unistd.h>

const std::string & status_name1(int code)
{
    static std::unordered_map<int, std::string> status_table {
        { 0, "UNKNOWN" },
        { 100, "Continue" },
        { 101, "Switching Protocols" },
        { 200, "OK" },
        { 201, "Created" },
        { 202, "Accepted" },
        { 203, "Non-Authoritative Information" },
        { 204, "No Content" },
        { 205, "Reset Content" },
        { 206, "Partial Content" },
        { 300, "Multiple Choices" },
        { 301, "Moved Permanently" },
        { 302, "Found" },
        { 303, "See Other" },
        { 304, "Not Modified" },
        { 305, "Use Proxy" },
        { 307, "Temporary Redirect" },
        { 400, "Bad Request" },
        { 401, "Unauthorized" },
        { 402, "Payment Required" },
        { 403, "Forbidden" },
        { 404, "Not Found" },
        { 405, "Method Not Allowed" },
        { 406, "Not Acceptable" },
        { 407, "Proxy Authentication Required" },
        { 408, "Request Timeout" },
        { 409, "Conflict" },
        { 410, "Gone" },
        { 411, "Length Required" },
        { 412, "Precondition Failed" },
        { 413, "Request Entity Too Large" },
        { 414, "Request-URI Too Long" },
        { 415, "Unsupported Media Type" },
        { 416, "Requested Range Not Satisfiable" },
        { 417, "Expectation Failed" },
        { 499, "Rotten session" },
        { 500, "Internal Server Error" },
        { 501, "Not Implemented" },
        { 502, "Bad Gateway" },
        { 503, "Service Unavailable" },
        { 504, "Gateway Timeout" },
        { 505, "HTTP Version Not Supported" },
    };

    auto it = status_table.find(code);

    if (it != status_table.end())
        return it->second;

    return status_table.at(0);
}

const std::string & status_name2(int code)
{
    static std::unordered_map<int, std::string> status_table {
        { 0, "UNKNOWN" },
        { 100, "Continue" },
        { 101, "Switching Protocols" },
        { 200, "OK" },
        { 201, "Created" },
        { 202, "Accepted" },
        { 203, "Non-Authoritative Information" },
        { 204, "No Content" },
        { 205, "Reset Content" },
        { 206, "Partial Content" },
        { 300, "Multiple Choices" },
        { 301, "Moved Permanently" },
        { 302, "Found" },
        { 303, "See Other" },
        { 304, "Not Modified" },
        { 305, "Use Proxy" },
        { 307, "Temporary Redirect" },
        { 400, "Bad Request" },
        { 401, "Unauthorized" },
        { 402, "Payment Required" },
        { 403, "Forbidden" },
        { 404, "Not Found" },
        { 405, "Method Not Allowed" },
        { 406, "Not Acceptable" },
        { 407, "Proxy Authentication Required" },
        { 408, "Request Timeout" },
        { 409, "Conflict" },
        { 410, "Gone" },
        { 411, "Length Required" },
        { 412, "Precondition Failed" },
        { 413, "Request Entity Too Large" },
        { 414, "Request-URI Too Long" },
        { 415, "Unsupported Media Type" },
        { 416, "Requested Range Not Satisfiable" },
        { 417, "Expectation Failed" },
        { 499, "Rotten session" },
        { 500, "Internal Server Error" },
        { 501, "Not Implemented" },
        { 502, "Bad Gateway" },
        { 503, "Service Unavailable" },
        { 504, "Gateway Timeout" },
        { 505, "HTTP Version Not Supported" },
    };

    auto it = status_table.find(code);

    if (it != status_table.end())
        return it->second;

    return status_table.at(0);
}

const std::string & status_name3(int code)
{
    static std::unordered_map<int, std::string> status_table {
        { 0, "UNKNOWN" },
        { 100, "Continue" },
        { 101, "Switching Protocols" },
        { 200, "OK" },
        { 201, "Created" },
        { 202, "Accepted" },
        { 203, "Non-Authoritative Information" },
        { 204, "No Content" },
        { 205, "Reset Content" },
        { 206, "Partial Content" },
        { 300, "Multiple Choices" },
        { 301, "Moved Permanently" },
        { 302, "Found" },
        { 303, "See Other" },
        { 304, "Not Modified" },
        { 305, "Use Proxy" },
        { 307, "Temporary Redirect" },
        { 400, "Bad Request" },
        { 401, "Unauthorized" },
        { 402, "Payment Required" },
        { 403, "Forbidden" },
        { 404, "Not Found" },
        { 405, "Method Not Allowed" },
        { 406, "Not Acceptable" },
        { 407, "Proxy Authentication Required" },
        { 408, "Request Timeout" },
        { 409, "Conflict" },
        { 410, "Gone" },
        { 411, "Length Required" },
        { 412, "Precondition Failed" },
        { 413, "Request Entity Too Large" },
        { 414, "Request-URI Too Long" },
        { 415, "Unsupported Media Type" },
        { 416, "Requested Range Not Satisfiable" },
        { 417, "Expectation Failed" },
        { 499, "Rotten session" },
        { 500, "Internal Server Error" },
        { 501, "Not Implemented" },
        { 502, "Bad Gateway" },
        { 503, "Service Unavailable" },
        { 504, "Gateway Timeout" },
        { 505, "HTTP Version Not Supported" },
    };

    auto it = status_table.find(code);

    if (it != status_table.end())
        return it->second;

    return status_table.at(0);
}

const std::string & status_name4(int code)
{
    static std::unordered_map<int, std::string> status_table {
        { 0, "UNKNOWN" },
        { 100, "Continue" },
        { 101, "Switching Protocols" },
        { 200, "OK" },
        { 201, "Created" },
        { 202, "Accepted" },
        { 203, "Non-Authoritative Information" },
        { 204, "No Content" },
        { 205, "Reset Content" },
        { 206, "Partial Content" },
        { 300, "Multiple Choices" },
        { 301, "Moved Permanently" },
        { 302, "Found" },
        { 303, "See Other" },
        { 304, "Not Modified" },
        { 305, "Use Proxy" },
        { 307, "Temporary Redirect" },
        { 400, "Bad Request" },
        { 401, "Unauthorized" },
        { 402, "Payment Required" },
        { 403, "Forbidden" },
        { 404, "Not Found" },
        { 405, "Method Not Allowed" },
        { 406, "Not Acceptable" },
        { 407, "Proxy Authentication Required" },
        { 408, "Request Timeout" },
        { 409, "Conflict" },
        { 410, "Gone" },
        { 411, "Length Required" },
        { 412, "Precondition Failed" },
        { 413, "Request Entity Too Large" },
        { 414, "Request-URI Too Long" },
        { 415, "Unsupported Media Type" },
        { 416, "Requested Range Not Satisfiable" },
        { 417, "Expectation Failed" },
        { 499, "Rotten session" },
        { 500, "Internal Server Error" },
        { 501, "Not Implemented" },
        { 502, "Bad Gateway" },
        { 503, "Service Unavailable" },
        { 504, "Gateway Timeout" },
        { 505, "HTTP Version Not Supported" },
    };

    auto it = status_table.find(code);

    if (it != status_table.end())
        return it->second;

    return status_table.at(0);
}

const std::string & status_name5(int code)
{
    static std::unordered_map<int, std::string> status_table {
        { 0, "UNKNOWN" },
        { 100, "Continue" },
        { 101, "Switching Protocols" },
        { 200, "OK" },
        { 201, "Created" },
        { 202, "Accepted" },
        { 203, "Non-Authoritative Information" },
        { 204, "No Content" },
        { 205, "Reset Content" },
        { 206, "Partial Content" },
        { 300, "Multiple Choices" },
        { 301, "Moved Permanently" },
        { 302, "Found" },
        { 303, "See Other" },
        { 304, "Not Modified" },
        { 305, "Use Proxy" },
        { 307, "Temporary Redirect" },
        { 400, "Bad Request" },
        { 401, "Unauthorized" },
        { 402, "Payment Required" },
        { 403, "Forbidden" },
        { 404, "Not Found" },
        { 405, "Method Not Allowed" },
        { 406, "Not Acceptable" },
        { 407, "Proxy Authentication Required" },
        { 408, "Request Timeout" },
        { 409, "Conflict" },
        { 410, "Gone" },
        { 411, "Length Required" },
        { 412, "Precondition Failed" },
        { 413, "Request Entity Too Large" },
        { 414, "Request-URI Too Long" },
        { 415, "Unsupported Media Type" },
        { 416, "Requested Range Not Satisfiable" },
        { 417, "Expectation Failed" },
        { 499, "Rotten session" },
        { 500, "Internal Server Error" },
        { 501, "Not Implemented" },
        { 502, "Bad Gateway" },
        { 503, "Service Unavailable" },
        { 504, "Gateway Timeout" },
        { 505, "HTTP Version Not Supported" },
    };

    auto it = status_table.find(code);

    if (it != status_table.end())
        return it->second;

    return status_table.at(0);
}

const std::string & status_name6(int code)
{
    static std::unordered_map<int, std::string> status_table {
        { 0, "UNKNOWN" },
        { 100, "Continue" },
        { 101, "Switching Protocols" },
        { 200, "OK" },
        { 201, "Created" },
        { 202, "Accepted" },
        { 203, "Non-Authoritative Information" },
        { 204, "No Content" },
        { 205, "Reset Content" },
        { 206, "Partial Content" },
        { 300, "Multiple Choices" },
        { 301, "Moved Permanently" },
        { 302, "Found" },
        { 303, "See Other" },
        { 304, "Not Modified" },
        { 305, "Use Proxy" },
        { 307, "Temporary Redirect" },
        { 400, "Bad Request" },
        { 401, "Unauthorized" },
        { 402, "Payment Required" },
        { 403, "Forbidden" },
        { 404, "Not Found" },
        { 405, "Method Not Allowed" },
        { 406, "Not Acceptable" },
        { 407, "Proxy Authentication Required" },
        { 408, "Request Timeout" },
        { 409, "Conflict" },
        { 410, "Gone" },
        { 411, "Length Required" },
        { 412, "Precondition Failed" },
        { 413, "Request Entity Too Large" },
        { 414, "Request-URI Too Long" },
        { 415, "Unsupported Media Type" },
        { 416, "Requested Range Not Satisfiable" },
        { 417, "Expectation Failed" },
        { 499, "Rotten session" },
        { 500, "Internal Server Error" },
        { 501, "Not Implemented" },
        { 502, "Bad Gateway" },
        { 503, "Service Unavailable" },
        { 504, "Gateway Timeout" },
        { 505, "HTTP Version Not Supported" },
    };

    auto it = status_table.find(code);

    if (it != status_table.end())
        return it->second;

    return status_table.at(0);
}

const std::string & status_name7(int code)
{
    static std::unordered_map<int, std::string> status_table {
        { 0, "UNKNOWN" },
        { 100, "Continue" },
        { 101, "Switching Protocols" },
        { 200, "OK" },
        { 201, "Created" },
        { 202, "Accepted" },
        { 203, "Non-Authoritative Information" },
        { 204, "No Content" },
        { 205, "Reset Content" },
        { 206, "Partial Content" },
        { 300, "Multiple Choices" },
        { 301, "Moved Permanently" },
        { 302, "Found" },
        { 303, "See Other" },
        { 304, "Not Modified" },
        { 305, "Use Proxy" },
        { 307, "Temporary Redirect" },
        { 400, "Bad Request" },
        { 401, "Unauthorized" },
        { 402, "Payment Required" },
        { 403, "Forbidden" },
        { 404, "Not Found" },
        { 405, "Method Not Allowed" },
        { 406, "Not Acceptable" },
        { 407, "Proxy Authentication Required" },
        { 408, "Request Timeout" },
        { 409, "Conflict" },
        { 410, "Gone" },
        { 411, "Length Required" },
        { 412, "Precondition Failed" },
        { 413, "Request Entity Too Large" },
        { 414, "Request-URI Too Long" },
        { 415, "Unsupported Media Type" },
        { 416, "Requested Range Not Satisfiable" },
        { 417, "Expectation Failed" },
        { 499, "Rotten session" },
        { 500, "Internal Server Error" },
        { 501, "Not Implemented" },
        { 502, "Bad Gateway" },
        { 503, "Service Unavailable" },
        { 504, "Gateway Timeout" },
        { 505, "HTTP Version Not Supported" },
    };

    auto it = status_table.find(code);

    if (it != status_table.end())
        return it->second;

    return status_table.at(0);
}

const std::string & status_name8(int code)
{
    static std::unordered_map<int, std::string> status_table {
        { 0, "UNKNOWN" },
        { 100, "Continue" },
        { 101, "Switching Protocols" },
        { 200, "OK" },
        { 201, "Created" },
        { 202, "Accepted" },
        { 203, "Non-Authoritative Information" },
        { 204, "No Content" },
        { 205, "Reset Content" },
        { 206, "Partial Content" },
        { 300, "Multiple Choices" },
        { 301, "Moved Permanently" },
        { 302, "Found" },
        { 303, "See Other" },
        { 304, "Not Modified" },
        { 305, "Use Proxy" },
        { 307, "Temporary Redirect" },
        { 400, "Bad Request" },
        { 401, "Unauthorized" },
        { 402, "Payment Required" },
        { 403, "Forbidden" },
        { 404, "Not Found" },
        { 405, "Method Not Allowed" },
        { 406, "Not Acceptable" },
        { 407, "Proxy Authentication Required" },
        { 408, "Request Timeout" },
        { 409, "Conflict" },
        { 410, "Gone" },
        { 411, "Length Required" },
        { 412, "Precondition Failed" },
        { 413, "Request Entity Too Large" },
        { 414, "Request-URI Too Long" },
        { 415, "Unsupported Media Type" },
        { 416, "Requested Range Not Satisfiable" },
        { 417, "Expectation Failed" },
        { 499, "Rotten session" },
        { 500, "Internal Server Error" },
        { 501, "Not Implemented" },
        { 502, "Bad Gateway" },
        { 503, "Service Unavailable" },
        { 504, "Gateway Timeout" },
        { 505, "HTTP Version Not Supported" },
    };

    auto it = status_table.find(code);

    if (it != status_table.end())
        return it->second;

    return status_table.at(0);
}

int main(int argc, char ** argv)
{
    std::vector<std::thread> thread_pool;
    auto guard_variable = std::make_shared<std::atomic<int>>(0);

    for (int i = 0; i < 8; ++i) {
        thread_pool.push_back(std::thread([guard_variable] {
            while (!guard_variable->load(std::memory_order_relaxed)) {
                /// Noop.
            }

            int i = 0;
            i += status_name1(200).length();
            i += status_name2(200).length();
            i += status_name3(200).length();
            i += status_name4(200).length();
            i += status_name5(200).length();
            i += status_name6(200).length();
            i += status_name7(200).length();
            i += status_name8(200).length();
            std::cout << i << std::endl;
        }));
    }

    usleep(50000);
    guard_variable->store(1, std::memory_order_relaxed);

    for (auto && thread : thread_pool) {
        thread.join();
    }

    return 0;
}

除了在该测试机器上之外,此代码在其他任何地方都可以正常工作。无论我使用什么版本的 clang (3.3, 3.6),这个测试程序都会因为异常立即崩溃,在不同的时间在不同的status_name* 函数中崩溃。

什么可能导致这种行为?是 CPU 缓存不一致还是 libc++ 中的错误?

【问题讨论】:

  • 你应该使用const char*而不是std::string作为映射类型。由于您无论如何都在存储文字,因此效率更高。
  • @JohnZwinck 我完全错过了你的所有观点......我使用std::string 因为我想要const std::string &amp; 作为函数调用的结果,即“静态”字符串的集合。
  • @GreenScape:通过散列表查找这个特定的数据集将比使用排序向量更慢且更占用空间。但是,嘿,这取决于你!哈希表在理论上更快,但在实践中并非总是如此。
  • @GreenScape,如果你制作地图const 有什么不同吗?我同意上面的评论,用std::lower_bound 搜索的排序向量对于这个用例会更快(并且也将避免第二次查找“未知”的情况,因为那将只是status_table.front())。另外,为什么您的guard_variable 在测试用例中使用shared_ptr

标签: multithreading c++11 clang libc++


【解决方案1】:

Clang 从 2.9 per http://clang.llvm.org/cxx_status.html 开始支持“神奇的静态”(N2660 动态初始化和并发销毁)。

与编译器或直接代码中的错误相比,代码中其他地方的错误更有可能破坏程序状态,导致status_table 显示为已初始化但为空。

【讨论】:

  • 在代码转储中它被初始化,所有元素都在里面。当然,迭代器比较和异常传播之间有一个窗口,但可能性有多大?
  • 不幸的是,更新中附加的测试用例放弃了这个结论。在那里我隔离了一段有问题的代码。
  • @GreenScape 如果它只发生在特定的机器上,那台机器可能有一个坏的 libstdc++(它提供了__cxa_guard_acquire 等)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-26
相关资源
最近更新 更多