我有两个班级。一个是全局单例(用作通用 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()
时甚至很重要???