Вы должны простить довольно большой блок кода, но я считаю, что это почти минимальное воспроизведение моей проблемы. Проблема не только в example.com
, но и на многих других сайтах.
Если у меня есть 4 потока, активно выполняющих сетевые запросы, curl работает на 100% нормально.
Если я добавлю еще один поток, этот поток будет выполняться примерно в 10 раз быстрее. Я чувствую, что, должно быть, упускаю что-то очевидное, но сейчас это ускользает от меня.
ОБНОВЛЕНИЕ с дополнительной информацией: эти тесты выполняются на виртуальной машине. Независимо от количества ядер, доступных машине, четыре запроса занимают ~100 мс, а остальные — ~5500 мс.
ОБНОВЛЕНИЕ 2. На самом деле, я был не прав в одном аспекте, это не всегда 4
/n-4
распределение. ядро по крайней мере казалось относительно согласованным) — вот фрагмент результатов, когда потоки возвращают свою задержку (мс) вместо своего http-кода при работе на 4-ядерной виртуальной машине:
191 191
198 198 167
209 208 202 208
215 207 214 209 209
5650 213 5649 222 193 207
206 201 164 205 201 201 205
5679 5678 5666 5678 216 173 205 175
5691 212 179 206 5685 5688 211 5691 5680
5681 199 210 5678 5663 213 5679 212 5666 428
ОБНОВЛЕНИЕ 3: я собрал curl и openssl с нуля, удалил блокировку (поскольку openssl 1.1.0g этого не требует), и проблема не устранена. (Проверка работоспособности/подтверждено следующим):
std::cout << "CURL:\n " << curl_version_info(CURLVERSION_NOW)->ssl_version
<< "\n";
std::cout << "SSLEAY:\n " << SSLeay_version(SSLEAY_VERSION) << "\n";
вывод:
CURL:
OpenSSL/1.1.0g
SSLEAY:
OpenSSL 1.1.0g 2 Nov 2017
С примерами задержек:
191 191
197 197 196
210 210 201 210
212 212 199 200 165
5656 5654 181 214 181 212
5653 5651 5647 211 206 205 162
5681 5674 5669 165 201 204 201 5681
5880 5878 5657 5662 197 209 5664 173 174
5906 5653 5664 5905 5663 173 5666 173 165 204
ОБНОВЛЕНИЕ 4. При установке CURLOPT_CONNECTTIMEOUT_MS
равным x
x
становится верхним пределом времени, необходимого для возврата.
ОБНОВЛЕНИЕ 5, САМОЕ ВАЖНОЕ:
Запуск программы под strace -T ./a.out 2>&1 | vim -
с 5 потоками, когда у программы был только 1 медленный запрос, дал две очень медленные строки. Это были два вызова одного и того же фьютекса, один из которых занял больше времени, чем второй, но оба заняли больше времени, чем все вызовы других фьютексов (большинство из них были 0,000011 мс, эти два вызова заняли 5,4 и 0,2 секунды, чтобы разблокировать) .
Кроме того, я убедился, что медлительность была полностью в curl_easy_perform
.
futex(0x7efcb66439d0, FUTEX_WAIT, 3932, NULL) = 0 <5.390086>
futex(0x7efcb76459d0, FUTEX_WAIT, 3930, NULL) = 0 <0.204908>
Наконец, покопавшись в исходном коде, я обнаружил, что ошибка где-то в поиске DNS. Замена имен хостов IP-адресами — это решение проблемы, где бы она ни была.
-----------
Ниже мое минимальное воспроизведение/выгонка проблемы, скомпилированная с помощью g++ -lpthread -lcurl -lcrypto main.cc
, связанная с версиями openssl и libcurl, собранными из исходников.
#include <chrono>
#include <iomanip>
#include <iostream>
#include <thread>
#include <vector>
#include <curl/curl.h>
#include <openssl/crypto.h>
size_t NoopWriteFunction(void *buffer, size_t size, size_t nmemb, void *userp) {
return size * nmemb;
};
int GetUrl() {
CURL *hnd = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_URL, "https://www.example.com/");
curl_easy_setopt(hnd, CURLOPT_HEADERFUNCTION, NoopWriteFunction);
curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, NoopWriteFunction);
curl_easy_setopt(hnd, CURLOPT_SSH_KNOWNHOSTS, "/home/web/.ssh/known_hosts");
CURLcode ret = curl_easy_perform(hnd);
long http_code = 0;
curl_easy_getinfo(hnd, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(hnd);
hnd = NULL;
if (ret != CURLE_OK) {
return -ret;
}
return http_code;
}
int main() {
curl_global_init(CURL_GLOBAL_ALL);
for (int i = 1; i < 10; i++) {
std::vector<std::thread> threads;
int response_code[10]{};
auto clock = std::chrono::high_resolution_clock();
auto start = clock.now();
threads.resize(i);
for (int j = 0; j < i; j++) {
threads.emplace_back(std::thread(
[&response_code](int x) { response_code[x] = GetUrl(); }, j));
}
for (auto &t : threads) {
if (t.joinable()) {
t.join();
}
}
auto end = clock.now();
int time_to_execute =
std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
.count();
std::cout << std::setw(10) << time_to_execute;
for (int j = 0; j < i; j++) {
std::cout << std::setw(5) << response_code[j];
}
std::cout << "\n";
}
}
И когда я запускаю программу на своей машине, я получаю следующий результат (я могу изменить домен на любой, результаты ~ те же):
123 200
99 200 200
113 200 200 200
119 200 200 200 200
5577 200 200 200 200 200
5600 200 200 200 200 200 200
5598 200 200 200 200 200 200 200
5603 200 200 200 200 200 200 200 200
5606 200 200 200 200 200 200 200 200 200
А вот моя версия curl и версия openssl:
$curl --version
curl 7.52.1 (x86_64-pc-linux-gnu) libcurl/7.52.1 OpenSSL/1.0.2l zlib/1.2.8 libidn2/0.16 libpsl/0.17.0 (+libidn2/0.16) libssh2/1.7.0 nghttp2/1.18.1 librtmp/2.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL
$ openssl version
OpenSSL 1.1.0f 25 May 2017
lscpu
перечисляет 1 ЦП, 1 поток на ядро и 1 ядро на сокет с 1 сокетом. - person druckermanly   schedule 01.02.20184
потоки, занимающие ~ 100 мс, иn-4
потоки, занимающие ~ 5500 мс. - person druckermanly   schedule 01.02.2018uname -a
дает:Linux web 4.9.0-4-amd64 #1 SMP Debian 4.9.65-3+deb9u1 (2017-12-23) x86_64 GNU/Linux
, аcat /proc/version
даетLinux version 4.9.0-4-amd64 ([email protected]) (gcc version 6.3.0 20170516 (Debian 6.3.0-18) ) #1 SMP Debian 4.9.65-3+deb9u1 (2017-12-23)
. - person druckermanly   schedule 03.02.2018thread_t
). Безопаснее возвращать указатель на то, что на самом деле не меняется... подумайте:static unsigned long *thread_id(void) { static __thread unsigned long ret = (unsigned long)pthread_self(); return &ret; }
. - person Myst   schedule 03.02.2018init_locks()
из исходного кода? Это не требуется для примера, и я подозреваю, что это может вызвать кривую работу OpenSSL (особенно еслиcurl_global_init
вызывает какие-либо функции библиотеки OpenSSL, что может вызвать проблему с порядком). - person Myst   schedule 03.02.2018-pthread
вместо-lpthread
? - person YSC   schedule 05.02.2018