thread_local“单例”-CURL 线程中的段错误

问题描述 投票:0回答:0

我有两个班级。一个是全局单例(用作通用 DBmanager - 表检查/创建等),另一种是每线程单例(用作数据管理器 - 准备好的语句和数据缓冲区)。两者都是通过以下方式实现的:

编辑:请参阅末尾的可重现示例

我也有一个函数,比方说,

arbitrary_curl_something()
使用简单句柄进行调用。

我正在使用 GCC/G++ 12.1 通过 Visual Studio 远程开发在 Ubuntu 服务器上进行编译。

它会导致段错误,就在 curl 尝试执行时,即使

instance()
之后被调用。但是,当我在
instance()
中创建实例时,只需
static
而不是
thread_local
它工作正常。

段错误显示在

res_mkquery.c
中发生,调用堆栈如下:

libc.so.6!__GI___res_context_mkquery(struct resolv_context * ctx, int op, const char * dname, int class, int type, const unsigned char * data, unsigned char * buf, int buflen) Line 117
    at resolv\resolv\res_mkquery.c(117)
libc.so.6!__GI___res_context_query(struct resolv_context * ctx, const char * name, int class, int type, unsigned char * answer, int anslen, unsigned char ** answerp, unsigned char ** answerp2, int * nanswerp2, int * resplen2, int * answerp2_malloced) Line 139
    at resolv\resolv\res_query.c(139)
libc.so.6!__res_context_querydomain(int * answerp2_malloced, int * resplen2, int * nanswerp2, unsigned char ** answerp2, unsigned char ** answerp, int anslen, unsigned char * answer, int type, int class, const char * domain, const char * name, struct resolv_context * ctx) Line 625
    at resolv\resolv\res_query.c(625)
libc.so.6!__GI___res_context_search(struct resolv_context * ctx, const char * name, int class, int type, unsigned char * answer, int anslen, unsigned char ** answerp, unsigned char ** answerp2, int * nanswerp2, int * resplen2, int * answerp2_malloced) Line 381
    at resolv\resolv\res_query.c(381)
libc.so.6!__GI__nss_dns_gethostbyname4_r(const char * name, struct gaih_addrtuple ** pat, char * buffer, size_t buflen, int * errnop, int * herrnop, int32_t * ttlp) Line 373
    at resolv\nss_dns\dns-host.c(373)
libc.so.6!gaih_inet(const char * name, const struct gaih_service * service, const struct addrinfo * req, struct addrinfo ** pai, unsigned int * naddrs, struct scratch_buffer * tmpbuf) Line 747
    at \sysdeps\posix\getaddrinfo.c(747)
libc.so.6!__GI_getaddrinfo(const char * name, const char * service, const struct addrinfo * hints, struct addrinfo ** pai) Line 2240
    at \sysdeps\posix\getaddrinfo.c(2240)
libcurl.so.4![Unknown/Just-In-Time compiled code]
libc.so.6!start_thread(void * arg) Line 442
    at nptl\nptl\pthread_create.c(442)
libc.so.6!clone3() Line 81
    at \sysdeps\unix\sysv\linux\x86_64\clone3.S(81)

libc.so.6!___ns_name_pack(const unsigned char * src, unsigned char * dst, int dstsiz, const unsigned char ** dnptrs, const unsigned char ** lastdnptr) Line 106
    at resolv\resolv\ns_name_pack.c(106)
libc.so.6!___ns_name_compress(const char * src, unsigned char * dst, size_t dstsiz, const unsigned char ** dnptrs, const unsigned char ** lastdnptr) Line 41
    at resolv\resolv\ns_name_compress.c(41)
libc.so.6!__GI___res_context_mkquery(struct resolv_context * ctx, int op, const char * dname, int class, int type, const unsigned char * data, unsigned char * buf, int buflen) Line 153
    at resolv\resolv\res_mkquery.c(153)
libc.so.6!__GI___res_context_query(struct resolv_context * ctx, const char * name, int class, int type, unsigned char * answer, int anslen, unsigned char ** answerp, unsigned char ** answerp2, int * nanswerp2, int * resplen2, int * answerp2_malloced) Line 139
    at resolv\resolv\res_query.c(139)
libc.so.6!__res_context_querydomain(int * answerp2_malloced, int * resplen2, int * nanswerp2, unsigned char ** answerp2, unsigned char ** answerp, int anslen, unsigned char * answer, int type, int class, const char * domain, const char * name, struct resolv_context * ctx) Line 625
    at resolv\resolv\res_query.c(625)
libc.so.6!__GI___res_context_search(struct resolv_context * ctx, const char * name, int class, int type, unsigned char * answer, int anslen, unsigned char ** answerp, unsigned char ** answerp2, int * nanswerp2, int * resplen2, int * answerp2_malloced) Line 381
    at resolv\resolv\res_query.c(381)
libc.so.6!__GI__nss_dns_gethostbyname4_r(const char * name, struct gaih_addrtuple ** pat, char * buffer, size_t buflen, int * errnop, int * herrnop, int32_t * ttlp) Line 373
    at resolv\nss_dns\dns-host.c(373)
libc.so.6!gaih_inet(const char * name, const struct gaih_service * service, const struct addrinfo * req, struct addrinfo ** pai, unsigned int * naddrs, struct scratch_buffer * tmpbuf) Line 747
    at \sysdeps\posix\getaddrinfo.c(747)
libc.so.6!__GI_getaddrinfo(const char * name, const char * service, const struct addrinfo * hints, struct addrinfo ** pai) Line 2240
    at \sysdeps\posix\getaddrinfo.c(2240)
libcurl.so.4![Unknown/Just-In-Time compiled code]
libc.so.6!start_thread(void * arg) Line 442
    at nptl\nptl\pthread_create.c(442)
libc.so.6!clone3() Line 81
    at \sysdeps\unix\sysv\linux\x86_64\clone3.S(81)

我不知道这个问题是基于 libcurl、基于其他库、基于线程还是什么,我正在慢慢地失去理智。我什至在这个网站上看到有人在做线程本地单例,它对他们有用,所以我不知道。

重现示例:

#include <cstddef>
#include <string>
#include <iostream>
#include <array>
#include <vector>
#include <memory>

//#define CURL_STATICLIB // doesnt matter
#include <curl/curl.h>
#include <curl/easy.h>

using std::size_t;
using std::cout;
using std::endl;

constexpr size_t cnt_sml = 6;
constexpr size_t cnt_big = 1 << 16;

class ThrdLcl
{
    using qr_ptr = std::unique_ptr<int32_t>; // some sql query class
    using dt_ptr = std::unique_ptr<int64_t>; // some data storage struct

private:
    std::array<std::array<            qr_ptr,  cnt_big>, cnt_sml> queries;
    std::array<std::array<std::vector<dt_ptr>, cnt_big>, cnt_sml> buffer;

    ThrdLcl()
    {
        cout << "ThrdLcl constructed!" << endl;
    }
    ~ThrdLcl()
    {
        cout << "ThrdLcl destructed!" << endl;
    }
    ThrdLcl(const ThrdLcl&) = delete;
    ThrdLcl(const ThrdLcl&&) = delete;
    ThrdLcl& operator=(const ThrdLcl&) = delete;
    ThrdLcl& operator=(const ThrdLcl&&) = delete;

public:
    static ThrdLcl* i()
    {
        thread_local ThrdLcl instance;
        return &instance;
    }
};

void do_curl_bullshit()
{
    cout << "Checking CURL..." << endl;
    CURL* ch = curl_easy_init();
    curl_easy_setopt(ch, CURLOPT_URL, "https://api.ipify.org"); // any url
    curl_easy_perform(ch);
    curl_easy_cleanup(ch);
}

int main(int argc, char* argv[])
{
    ThrdLcl::i(); // doesnt matter if here
    curl_global_init(CURL_GLOBAL_NOTHING);
    do_curl_bullshit();
    ThrdLcl::i(); // ...or here
    curl_global_cleanup();
    return 0;
}

当然需要链接libcurl。使用 g++ 12.1、C 标准 11 和 C++ 标准 20 编译。 当实例变为静态时,一切正常(仅在将数据大小增加千倍之后,才会出现链接器错误)。 当实例是 thread_local 时,只要指定缓冲区大小的一半,它就可以正常工作。当我自己创建一个

std::thread
(而不是调用CURL)时,它工作正常。当实例在堆上创建并再次使用 CURL 存储在
unique_ptr
中时,一切正常。

然而这引发了问题:
为什么 CURL 为非并行接口创建线程?
为什么特别是segfault
为什么仅通过更改类内变量的数量(而不是它们的总大小)就可以将跟踪从第 1 个更改为第 2 个?
为什么在 CURL 线程内没有调用

instance()
时甚至很重要???

c++ multithreading singleton libcurl
© www.soinside.com 2019 - 2024. All rights reserved.