
Sissejuhatus
(ühe lõimega ) — see on musternäidis, mis on mõeldud kõrgkoormusega tarkvara kirjutamiseks ja seda kasutatakse paljude populaarsete lahenduste puhul:
- …
Selles artiklis vaatame I/O reaatori tagamaid ja selle tööpõhimõtteid, kirjutame rakenduse vähem kui 200 koodireaga ja paneme lihtsa HTTP-serveri töötlema üle 40 miljoni päringu minutis.
Eessõna
- Artikkel on kirjutatud selleks, et aidata mõista I/O reaktori toimimist ja seega teadvustada riske selle kasutamisel.
- Artikli mõistmiseks on vajalik teadmised ja vähene kogemus võrgurakenduste arendamisel.
- Kogu kood on kirjutatud keeles C rangelt vastavalt (hoiatavalt: pikk PDF) Linuxis ja on saadaval .
Miks on see vajalik?
Interneti populaarsuse kasvades on veebiserveritel muutunud vajalikuks töödelda samal ajal suures mahus ühendusi, mille tõttu on proovitud kahte lähenemist: blokeerivat I/O suure hulga opsüsteemi lõimede abil ja mitteblokeerivat I/O koos sündmustest teavitamise süsteemiga, mida nimetatakse ka «süsteemi valijaks» (///etc).
Esimene lähenemine hõlmab uue operatsioonisüsteemi lõime loomist iga sissetuleva ühenduse jaoks. Selle puuduseks on halb skaleeritavus: operatsioonisüsteem peab tegema arvukaid ja . Need on kulukad operatsioonid ja võivad põhjustada olulise ühenduste arvu korral vaba RAM-i puudujääki.
Muudetud versioon eraldab (thread pool), takistades seeläbi süsteemi äkilist töö lõpetamist, kuid samas toob see esile uue probleemi: kui lõimede puhvrid on lukustatud pikaajaliste lugemistegevuste tõttu, ei saa teised sokid, mis on juba valmis andmeid vastu võtma, seda teha.
Teine lähenemine kasutab (süsteemide selektor), mida operatsioonisüsteem pakub. Käesolevas artiklis käsitletakse kõige sagedasemat tüüpi süsteemide selektorit, mis põhineb I/O operatsioonide valmidusest teavitamise (sündmuste, teavituste) alusel, mitte . Lihtsustatud näidet selle kasutamisest võib esitada järgmise plokiahelana:

Needused meetodite vahel on erinevus järgmine:
- Tõkestavad I/O operatsioonid peatavad kasutaja voolu kuni, kuni OS on korralikult aktiivsed baiti voos (, andmete vastuvõtt) või kui sisemistes kirjutusbufferites on piisavalt vaba ruumi edasise saatmise jaoks (andmete saatmine).
- Süsteemisüsteem mõne aja pärast teavitab programmi, et OS juba on defragmentinud IP paketid (TCP, andmete vastuvõtt) või piisavalt ruumi sisemistes kirjutusbufferites juba on saadaval (andmete saatmine).
Kokkuvõtteks võib öelda, et OS voolu reserveerimine igas I/O puhul on lihtsalt arvutusvõime raiskamine, kuna voolud ei tegele tegelikult kasuliku tööga (just sealt tuleneb mõisted ). Süsteemisüsteem lahendab selle probleemi, võimaldades kasutaja programmidel CPU ressursse oluliselt efektiivsemalt kasutada.
I/O reaktsiooni mudel
I/O reaktsioon toimib kui kiht süsteemisüsteemi ja kasutajakoodi vahel. Selle tööpõhimõte on kirjeldatud järgmise vooskeemiga:

- Tulet meelde, et sündmus on teade selle kohta, et kindel sokkel on võimeline sooritama mittesünkroonset I/O operatsiooni.
- Sündmuste töötleja on funktsioon, mida kutsub I/O reaktor üles, kui sündmus on saadud, ning see teostab seejärel mittesünkroonse I/O operatsiooni.
Oluline on märkida, et I/O reaktor on määratletud üheksatoonilisena, kuid ei ole midagi, mis takistaks kontseptsiooni kasutamist mitme niidi keskkonnas 1 niit: 1 reaktor, seega kasutatakse kõiki CPU tuumasid.
Rakendus
Avalik liides paigutame faili , ja rakenduse - faili . reactor.h koosneb järgmistest deklareerimistest:
Kuva deklareerimised failis reactor.h
typedef struct reactor Reactor;
/*
* Näitaja funktsioonile, mida I/O reaktor kutsub, kui
* süsteemi selektorilt saadakse sündmus.
*/
typedef void (*Callback)(void *arg, int fd, uint32_t events);
/*
* Tagastab `NULL` vea korral, mitte-`NULL` näitaja `Reactor`-le
* muul juhul.
*/
Reactor *reactor_new(void);
/*
* Vabastab süsteemi selektori, kõik registreeritud socketid antud
* hetkel ja I/O reaktori ise.
*
* Järgmised funktsioonid tagastavad -1 vea korral, 0 eduka korral.
*/
int reactor_destroy(Reactor *reactor);
int reactor_register(const Reactor *reactor, int fd, uint32_t interest,
Callback callback, void *callback_arg);
int reactor_deregister(const Reactor *reactor, int fd);
int reactor_reregister(const Reactor *reactor, int fd, uint32_t interest,
Callback callback, void *callback_arg);
/*
* Käivitab sündmuste tsükli ajapiiranguga `timeout`.
*
* See funktsioon edastab kontrolli kutsuvatele koodidele, kui eraldatud aeg on möödas
* või/ja kui registreeritud socketid puuduvad.
*/
int reactor_run(const Reactor *reactor, time_t timeout);I/O reaktori struktuur koosneb selektorist ja , mis seob iga socketi CallbackData (struktuuri sündmustöötlusest ja selle kasutaja argumendist).
Kuva Reactor ja CallbackData
struct reactor {
int epoll_fd;
GHashTable *table; // (int, CallbackData)
};
typedef struct {
Callback callback;
void *arg;
} CallbackData;Pange tähele, et oleme kasutanud immutamatute tüüpide käsitsemise funktsionaalsust näidiku kaudu. Esmalt reactor.h deklareerime struktuuri reactor, ja seejärel määratleme selle, takistades seeläbi kasutajal selgelt selle väljaid muuta. See on üks andmete peitmise mustritest reactor.c me määratleme, takistades seeläbi kasutajal selgelt selle väljade muutmist. See on üks mustritest , mis sobib kokku C-i semantikaga.
Funktsioonid reactor_register, reactor_deregister ja reactor_reregister uuendavad huvipakkuvate soklite ja vastavate sündmuste töötlejate nimekirja süsteemi selektoris ja hash-tabelis.
Näita registreerimisfunktsioone
#define REACTOR_CTL(reactor, op, fd, interest)
if (epoll_ctl(reactor->epoll_fd, op, fd,
&(struct epoll_event){.events = interest,
.data = {.fd = fd}}) == -1) {
perror("epoll_ctl");
return -1;
}
int reactor_register(const Reactor *reactor, int fd, uint32_t interest,
Callback callback, void *callback_arg) {
REACTOR_CTL(reactor, EPOLL_CTL_ADD, fd, interest)
g_hash_table_insert(reactor->table, int_in_heap(fd),
callback_data_new(callback, callback_arg));
return 0;
}
int reactor_deregister(const Reactor *reactor, int fd) {
REACTOR_CTL(reactor, EPOLL_CTL_DEL, fd, 0)
g_hash_table_remove(reactor->table, &fd);
return 0;
}
int reactor_reregister(const Reactor *reactor, int fd, uint32_t interest,
Callback callback, void *callback_arg) {
REACTOR_CTL(reactor, EPOLL_CTL_MOD, fd, interest)
g_hash_table_insert(reactor->table, int_in_heap(fd),
callback_data_new(callback, callback_arg));
return 0;
}Pärast seda, kui I/O reaktor on haaranud sündmuse deskriptoriga fd, kutsub see vastava sündmuste töötlejat, edastades fd, loodud sündmustest ja kasutaja viite void.
Näita funktsiooni reactor_run()
int reactor_run(const Reactor *reactor, time_t timeout) {
int result;
struct epoll_event *events;
if ((events = calloc(MAX_EVENTS, sizeof(*events))) == NULL)
abort();
time_t start = time(NULL);
while (true) {
time_t passed = time(NULL) - start;
int nfds =
epoll_wait(reactor->epoll_fd, events, MAX_EVENTS, timeout - passed);
switch (nfds) {
// Fehler
case -1:
perror("epoll_wait");
result = -1;
goto cleanup;
// Zeit abgelaufen
case 0:
result = 0;
goto cleanup;
// Erfolgreiche Operation
default:
// Invoke event handlers
for (int i = 0; i table, &fd);
callback->callback(callback->arg, fd, events[i].events);
}
}
}
cleanup:
free(events);
return result;
}Kokkuvõttes näeb kasutajakoodi funktsioonide ahel välja järgmine:

Ühe protsessori server
I/O reaktori testimiseks suure koormuse korral kirjutame lihtsa HTTP veebiserveri, mis vastab igale päringule pildi abil.
Lühike ülevaade HTTP protokollist
— on protokoll , mida kasutatakse peamiselt serveri ja brauseri suhtlemiseks.
HTTP-d on lihtne kasutada üle protokoll , saates ja vastu võttes määratletud formaadi sõnumeid .
Päringu formaat
CRLF
CRLF
CRLF
CRLF CRLFCRLF— on kahe sümboli järjestus:rjan, mis eraldab päringu esimest rida, pealkirjad ja andmed.<КОМАНДА>— üks järgmistestCONNECT,DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,TRACE. Brauser saadab meie serverile käsuGET, mis tähendab „Saada mulle faili sisu“.<URI>— . Näiteks, kui URI =/index.html, siis klient küsib saidi peamist lehte.<ВЕРСИЯ HTTP>— HTTP protokolli versioon formaadisHTTP/X.Y. Praegu enimkasutatav versioon onHTTP/1.1.<ЗАГОЛОВОК N>— on võti-väärtus paar formaadis:, saadetav serverile edasiseks analüüsimiseks.<ДАННЫЕ>— andmed, mida server vajab toimingu täitmiseks. Tihti on need lihtsalt või mõnes muus formaadis.
Vastuse formaat
CRLF
CRLF
CRLF
CRLF CRLF<КОД СТАТУСА>— see number, which represents the result of the operation. Our server will always return a status 200 (successful operation).<ОПИСАНИЕ СТАТУСА>— string representation of the status code. For status code 200 — this isOK.<ЗАГОЛОВОК N>— a header in the same format as in the request. We will return the headersContent-Length(size of the file) andContent-Type: text/html(type of returned data).<ДАННЫЕ>— data requested by the user. In our case, this is the path to the image in .
File (single-threaded server) includes the file , which contains the following function prototypes:
Show function prototypes in common.h
/*
* Обработчик событий, который вызовется после того, как сокет будет
* готов принять новое соединение.
*/
static void on_accept(void *arg, int fd, uint32_t events);
/*
* Обработчик событий, который вызовется после того, как сокет будет
* готов отправить HTTP ответ.
*/
static void on_send(void *arg, int fd, uint32_t events);
/*
* Обработчик событий, который вызовется после того, как сокет будет
* готов принять часть HTTP запроса.
*/
static void on_recv(void *arg, int fd, uint32_t events);
/*
* Переводит входящее соединение в неблокирующий режим.
*/
static void set_nonblocking(int fd);
/*
* Печатает переданные аргументы в stderr и выходит из процесса с
* кодом `EXIT_FAILURE`.
*/
static noreturn void fail(const char *format, ...);
/*
* Возвращает файловый дескриптор сокета, способного принимать новые
* TCP соединения.
*/
static int new_server(bool reuse_port);Also described is the functional macro SAFE_CALL() and the function fail(). The macro compares the expression value to an error, and if the condition is met, it calls the function fail():
#define SAFE_CALL(call, error)
do {
if ((call) == error) {
fail("%s", #call);
}
} while (false)Function fail() prints the passed arguments to the terminal (like ) and terminates the program with code EXIT_FAILURE:
static noreturn void fail(const char *format, ...) {
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fprintf(stderr, ": %sn", strerror(errno));
exit(EXIT_FAILURE);
}Function new_server() returns the file descriptor of the 'server' socket, created by system calls , ja ja suudab vastu võtta sisendeid blokkeerimata režiimis.
Kuva funktsioon new_server()
static int new_server(bool reuse_port) {
int fd;
SAFE_CALL((fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)),
-1);
if (reuse_port) {
SAFE_CALL(
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int)),
-1);
}
struct sockaddr_in addr = {.sin_family = AF_INET,
.sin_port = htons(SERVER_PORT),
.sin_addr = {.s_addr = inet_addr(SERVER_IPV4)},
.sin_zero = {0}};
SAFE_CALL(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), -1);
SAFE_CALL(listen(fd, SERVER_BACKLOG), -1);
return fd;
}- Pange tähele, et socket luuakse algselt blokkeerimata režiimis lipu kaudu
SOCK_NONBLOCK, et süsteemikõneon_accept()ei blokeeriks lõime täitmist.accept()Kui - Kui
reuse_porton väärttrue, siis konfigureerib see funktsioon socket'i valiku kaudu , et kasutada sama porti mitme lõime keskkonnas (vaata jaotist „Mitu lõime server”).
Ürituste käitleja on_accept() kutsutakse välja pärast seda, kui operatsioonisüsteem genereerib sündmuse EPOLLIN, antud juhul tähendab see, et uus ühendus võib olla vastu võetud. on_accept() võtab uue ühenduse, lülitab selle mittesulgurrežiimi ja registreerib sündmuste käitlejale on_recv() I/O reaktoris.
Näita funktsiooni on_accept()
static void on_accept(void *arg, int fd, uint32_t events) {
int incoming_conn;
SAFE_CALL((incoming_conn = accept(fd, NULL, NULL)), -1);
set_nonblocking(incoming_conn);
SAFE_CALL(reactor_register(reactor, incoming_conn, EPOLLIN, on_recv,
request_buffer_new()),
-1);
}Ürituste käitleja on_recv() kutsutakse välja pärast seda, kui operatsioonisüsteem genereerib sündmuse EPOLLIN, antud juhul tähendades, et registreeritud ühendus on_accept(), on andmete vastuvõtmiseks valmis.
on_recv() loeb andmeid ühendusest kuni HTTP päring on täielikult saadud, seejärel registreerib ta töötleja on_send() HTTP vastuse saatmiseks. Kui klient katkestas ühenduse, deregistreeritakse ja suletakse sokkel .
Näita funktsiooni on_recv()
static void on_recv(void *arg, int fd, uint32_t events) {
RequestBuffer *buffer = arg;
// Vastuvõtame sisendi, kuni recv tagastab 0 või vea
ssize_t nread;
while ((nread = recv(fd, buffer->data + buffer->size,
REQUEST_BUFFER_CAPACITY - buffer->size, 0)) > 0)
buffer->size += nread;
// Klient katkestas ühenduse
if (nread == 0) {
SAFE_CALL(reactor_deregister(reactor, fd), -1);
SAFE_CALL(close(fd), -1);
request_buffer_destroy(buffer);
return;
}
// read tagastas vea, mis pole blokaadi tõttu
// voos
if (errno != EAGAIN && errno != EWOULDBLOCK) {
request_buffer_destroy(buffer);
fail("read");
}
// Täielik HTTP päring on saadud kliendilt. Nüüd registreerime sündmuste
// töötleja andmete saatmiseks
if (request_buffer_is_complete(buffer)) {
request_buffer_clear(buffer);
SAFE_CALL(reactor_reregister(reactor, fd, EPOLLOUT, on_send, buffer),
-1);
}
}Ürituste käitleja on_send() kutsutakse välja pärast seda, kui operatsioonisüsteem genereerib sündmuse EPOLLOUT, mis tähendab, et registreeritud ühendus on_recv(), on andmete saatmiseks valmis. See funktsioon saadab HTTP vastuse, mis sisaldab HTML-i koos pildiga, kliendile ja seejärel muudab sündmuste töötlejat taas on_recv().
Näita funktsiooni on_send()
static void on_send(void *arg, int fd, uint32_t events) {
const char *content = "<img "
"src="https://habrastorage.org/webt/oh/wl/23/"
"ohwl23va3b-dioerobq_mbx4xaw.jpeg">";
char response[1024];
sprintf(response,
"HTTP/1.1 200 OK" CRLF "Content-Length: %zd" CRLF "Content-Type: "
"text/html" DOUBLE_CRLF "%s",
strlen(content), content);
SAFE_CALL(send(fd, response, strlen(response), 0), -1);
SAFE_CALL(reactor_reregister(reactor, fd, EPOLLIN, on_recv, arg), -1);
}Ja lõpuks, failis http_server.c, funktsioonis main() loome I/O reaktsiooni meetodi reactor_new(), loome serveri sokli ja registreerime selle, käivitame reaktsiooni kasutades reactor_run() täpselt ühe minuti, seejärel vabastame ressursid ja väljastame programmi.
Näita http_server.c
#include "reactor.h"
static Reactor *reactor;
#include "common.h"
int main(void) {
SAFE_CALL((reactor = reactor_new()), NULL);
SAFE_CALL(
reactor_register(reactor, new_server(false), EPOLLIN, on_accept, NULL),
-1);
SAFE_CALL(reactor_run(reactor, SERVER_TIMEOUT_MILLIS), -1);
SAFE_CALL(reactor_destroy(reactor), -1);
}Kontrollime, et kõik töötab nagu peab. Kompileerime (chmod a+x compile.sh && ./compile.sh projekti juures) ja käivitame isetehtud serveri, avame brauseris ja näeme seda, mida ootasime:

Jõudluse mõõtmine
Näita minu masina omadusi
$ screenfetch
MMMMMMMMMMMMMMMMMMMMMMMMMmds+. OS: Mint 19.1 tessa
MMm----::-:////////////oymNMd+` Kernel: x86_64 Linux 4.15.0-20-generic
MMd /++ -sNMd: Uptime: 2h 34m
MMNso/` dMM `.::-. .-::.` .hMN: Packages: 2217
ddddMMh dMM :hNMNMNhNMNMNh: `NMm Shell: bash 4.4.20
NMm dMM .NMN/-+MMM+/-NMN` dMM Resolution: 1920x1080
NMm dMM -MMm `MMM dMM. dMM DE: Cinnamon 4.0.10
NMm dMM -MMm `MMM dMM. dMM WM: Muffin
NMm dMM .mmd `mmm yMM. dMM WM Teema: Mint-Y-Dark (Mint-Y)
NMm dMM` ..` ... ydm. dMM GTK Teema: Mint-Y [GTK2/3]
hMM- +MMd/-------...-:sdds dMM Ikoni teema: Mint-Y
-NMm- :hNMNNNmdddddddddy/` dMM Fond: Noto Sans 9
-dMNs-``-::::-------.`` dMM CPU: Intel Core i7-6700 @ 8x 4GHz [52.0°C]
`/dMNmy+/:-------------:/yMMM GPU: NV136
./ydNMMMMMMMMMMMMMMMMMMMMM RAM: 2544MiB / 7926MiB
.MMMMMMMMMMMMMMMMMMMMõõdame ühesektorilise serveri jõudlust. Avame kaks terminali: ühes käivitame ./http_server, teises — . Minuti pärast kuvatakse teises terminalis järgmised statistikad:
$ wrk -c100 -d1m -t8 http://127.0.0.1:18470 -H "Host: 127.0.0.1:18470" -H "Accept-Language: et-EE,et;q=0.5" -H "Connection: keep-alive"
Käivitame 1m testi @ http://127.0.0.1:18470
8 lõime ja 100 ühendust
Lõime statistika Keskmine Std. kõrvalekalle Max +/- Std. kõrvalekalle
Latentsus 493.52us 76.70us 17.31ms 89.57%
Päringud/sec 24.37k 1.81k 29.34k 68.13%
11657769 päringut 1.00m jooksul, 1.60GB loetud
Päringud/sec: 193974.70
Ülekanded/sec: 27.19MBMeie ühesuunaline server suutis töödelda üle 11 miljoni päringu minutis, mis tulenes 100 ühendusest. Hea tulemus, aga kas seda saab paremaks muuta?
Mitu lõime sisaldav server
Nagu eelnevalt mainitud, saab I/O reaktorit luua eraldi lõimedes, kasutades ära kõiki CPU tuumasid. Rakendame seda lähenemist praktikas:
Kuva http_server_multithreaded.c
#include "reactor.h"
static Reactor *reactor;
#pragma omp threadprivate(reactor)
#include "common.h"
int main(void) {
#pragma omp parallel
{
SAFE_CALL((reactor = reactor_new()), NULL);
SAFE_CALL(reactor_register(reactor, new_server(true), EPOLLIN,
on_accept, NULL),
-1);
SAFE_CALL(reactor_run(reactor, SERVER_TIMEOUT_MILLIS), -1);
SAFE_CALL(reactor_destroy(reactor), -1);
}
}Nüüd omab iga lõim reaktorit:
static Reactor *reactor;
#pragma omp threadprivate(reactor)Pange tähele, et funktsiooni argumendina new_server() tuleb true. See tähendab, et me määrame serveri soketile valiku , et kasutada seda mitme lõime keskkonnas. Rohkem teavet leiate .
Teine katse
Nüüd mõõdame mitme lõime serveri jõudlust:
$ wrk -c100 -d1m -t8 http://127.0.0.1:18470 -H "Host: 127.0.0.1:18470" -H "Accept-Language: en-US,en;q=0.5" -H "Connection: keep-alive"
Ajal 1m test @ http://127.0.0.1:18470
8 lõime ja 100 ühendust
Lõime statistika Keskmine Std. kõrvalekalle Max +/– Std. kõrvalekalle
Latentsus 1.14ms 2.53ms 40.73ms 89.98%
Req/sec 79.98k 18.07k 154.64k 78.65%
38208400 päringut 1.00m jooksul, 5.23GB loetud
Päringud/sec: 635876.41
Ülekandega/sec: 89.14MB1 minuti jooksul töödeldud päringute arv kasvas ~3.28 korda! Kuid täieliku numbri saavutamiseks jäi puudu umbes ~ kaks miljonit, proovime seda parandada.
Esialgu vaatame statistikat, mis on genereeritud :
$ sudo perf stat -B -e task-clock,context-switches,cpu-migrations,page-faults,cycles,instructions,branches,branch-misses,cache-misses ./http_server_multithreaded
Jõudlusloendi statistika '. /http_server_multithreaded':
242446,314933 ülesande kella (msec) # 4,000 CPU-d on rakendatud
1 813 074 konteksti vahetusi # 0,007 M/sec
4 689 CPU-migratsioonid # 0,019 K/sec
254 lehe vead # 0,001 K/sec
895 324 830 170 tsüklid # 3,693 GHz
621 378 066 808 juhised # 0,69 insn per tsükkel
119 926 709 370 harud # 494,653 M/sec
3 227 095 669 haru vead # 2,69% kõikidest harudest
808 664 vahemälu vead
60,604330670 sekundit aega möödus, kompileerimine koos -march=native, , sisestuste arvu suurendamine , suurenemine MAX_EVENTS ja kasutamine EPOLLET ei toonud märkimisväärset jõudluse kasvu. Aga mis saab, kui suurendada samade ühenduste arvu?
Statistika 352 samal ajal ühenduse korral:
$ wrk -c352 -d1m -t8 http://127.0.0.1:18470 -H "Host: 127.0.0.1:18470" -H "Accept-Language: en-US,en;q=0.5" -H "Connection: keep-alive"
Töötamine 1m test @ http://127.0.0.1:18470
8 lõime ja 352 ühendust
Lõime Statistika Keskmine Std. dev. Max +/- Std. dev.
Latentsus 2.12ms 3.79ms 68.23ms 87.49%
Req/Sec 83.78k 12.69k 169.81k 83.59%
40006142 päringut 1,00m, 5.48GB lugemiseks
Päringud/sek: 665789.26
Ülekanded/sek: 93.34MBSoovitud tulemus saavutatud, koos sellega huvitav graafik, mis näitab ühe minuti jooksul töödeldud päringute arvu sõltuvust ühenduste arvust:

Näeme, et pärast paarisaja ühenduse saavutamist tõuseb töödeldud päringute arv mõlemal serveril järsult (mitme lõime variandi puhul on see paremini märgatav). Kas see on seotud Linuxi TCP/IP stack'i rakendusega? Ootame teie arvamusi selle graafiku käitumise ja mitme lõime ning ühe lõime variandi optimeerimiste kohta kommentaarides.
Kuidas kommentaarides, see jõudlustest ei peegelda I/O reaktori käitumist reaalses koormuses, kuna server suhtleb peaaegu alati andmebaasiga, genereerib logisid, kasutab krüptograafiat jne, mille tõttu koormus muutub ebaühtlaseks (dünaamiliseks). Testid koos kolmandate osapoolte komponentidega viiakse läbi I/O reaktori artiklis.
I/O reaktori puudused
Oluline on mõista, et I/O reaktoril on puudusi, sealhulgas:
- I/O reaktori kasutamine mitme lõime keskkonnas on keerulisem, kuna tuleb käsitsi vooge hallata.
- Praktika näitab, et enamasti on koormus ebaühtlane, mis võib viia olukorrani, kus üks voog töötleb, samas kui teine on tööga hõivatud.
- Kui üks sündmuse töötleja blokeerib voolu, blokeerib ka süsteemi selektor, mis võib tekitada raskesti tuvastatavaid vigu.
Need probleemid lahendab , mis sageli sisaldab ajakava, mis jaotab koormuse ühtlaselt lõimede puuli, ning millel on ka mugavam API. Räägime sellest hiljem teises artiklis.
Kokkuvõte
Selle lõppes meie teekond teooriast profiler'i väljastusse.
Ärge sellega piirduda, sest on veel palju teisi, mitte vähem huvitavaid lähenemisviise võrgu tarkvara kirjutamisele, erineva mugavuse ja kiirusetase. Huvi äratavad lingid on toodud allpool.
Kuni kohtumiseni!
Huvitavad projektid
- C
Mida veel lugeda?
Allikas: habr.com
