
Sissejuhatus
(üheline ) — see on musternäidis kõrge koormusega tarkvara kirjutamiseks, mida kasutatakse paljudes tuntud lahendustes:
- …
Käesolevas artiklis vaatleme I/O reaktori aluseid ja tööpõhimõtteid, kirjutame rakenduse vähem kui 200 koodi reaga ja paneme lihtsa HTTP serveri töötlema üle 40 miljoni päringu/min.
Eessõna
- Artikli eesmärk on aidata mõista I/O reaktori toimimist ja sellest tulenevaid riske selle kasutamisel.
- Artikli mõistmiseks on vajalik teadmised ja väike kogemus võrgurakenduste arendamisest.
- Kogu kood on kirjutatud C keeles rangelt (ole ettevaatlik: pikk PDF) järgi Linuxis ja on saadaval aadressil .
Miks see vajalik on?
Interneti populaarsuse tõusuga on veebiserveritele vaja olnud samaaegselt töödelda suurt hulka ühendusi, millega seoses on katsetatud kaht lähenemist: blokeeriv I/O suurte OS-i lõimede arvuga ja mitteblokeeriv I/O koos sündmuste teavitamis süsteemiga, mida nimetatakse ka „süsteemseks valijaks“ (///etc).
Esimene lähenemine hõlmas iga sissetuleva ühenduse jaoks uue OS-i lõime loomist. Selle puuduseks on halb skaleeritavus: operatsioonisüsteem peab tegema palju ja Muudetud versioon eraldab
fikseeritud arvu lõime Teine lähenemine kasutab
sündmuste teavitamissüsteemi toimingute lõpetamise teavitustel. Mõlema lähenemise vahel on erinevus järgmine:

Blokeerivad I/O toimingud
- peatavad kasutaja lõime пользовательский поток kuni AWS teeb sama asja ja ehitab selle ümber eduka äri, ja kui kliendid küsivad seda sõna otseses mõttes. Siiski tuleb teha teatavaid jõupingutusi, et sundida Google'it midagi toetama., kuni operatsioonisüsteem saabuvad bittide voolus (, andmete vastuvõtt) või kui kirjutamise sisemistes puhvrites ei ole piisavalt ruumi edasise saatmise jaoks (andmete saatmine).
- Süsteemi valija mõne aja pärast teavitab programmi, et operatsioonisüsteem juba defragmenteeris IP pakette (TCP, andmete vastuvõtt) või et kirjutamise sisemistes puhvrites on piisavalt ruumi juba kergesti kergesti (andmete saatmine).
Kokkuvõtteks, operatsioonisüsteemi voogude reserveerimine iga I/O jaoks on arvutusvõimsuse raiskamine, kuna vood ei tegele tõeliselt kasuliku tööga (sealt tuleneb mõisted ). Süsteemi valija lahendab selle probleemi, võimaldades kasutaja programmile kasutada CPU ressursse oluliselt efektiivsemalt.
I/O reaktori mudel
I/O reaktor toimib kihina süsteemi valija ja kasutaja koodi vahel. Selle tööprintsiipi kirjeldab järgmine vooskeem:

- Kordan, et sündmus on teade, et teatud sokkel on valmis teostama mitteblokeerivat I/O operatsiooni.
- Sündmuste töötleja on funktsioon, mille I/O reaktor kutsub esile sündmuse saamisel, mis seejärel sooritab mitteblokeeriva I/O operatsiooni.
Oluline on märkida, et I/O reaktor on määratletud ühe lõngana, kuid miski ei takista kasutamast kontsepti mitme lõnga keskkonnas seoses 1 lõng: 1 reaktor, kasutades seeläbi ära kõiki CPU südamikke.
Rakendamine
Avalik liides paigutame faili , ja rakenduse . reactor.h koosneb järgmistest deklareerimistest:
Kuva deklareerimised failis reactor.h
typedef struct reactor Reactor;
/*
* Funktsioonipointer, mis kutsub esile I/O reaktori, kui SYS selektoril
* on sündmus.
*/
typedef void (*Callback)(void *arg, int fd, uint32_t events);
/*
* Tagastab `NULL`, kui viga, mitte-`NULL` pointer `Reactor`-ile
* vastasel juhul.
*/
Reactor *reactor_new(void);
/*
* Vabastab süsteemi selektori, kõik registreeritud soketid antud
* hetkel ja I/O reaktori end.
*
* Järgmised funktsioonid tagastavad -1, kui viga, 0, kui õnnestus.
*/
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 ajavahemiku `timeout`-iga.
*
* See funktsioon edastab kontrolli kutsuvasse koodi, kui
* ettenähtud aeg on lõppenud või kui registreeritud sokette ei ole.
*/
int reactor_run(const Reactor *reactor, time_t timeout);I/O reaktori struktuur koosneb valijast ja , mis seondab iga soketi CallbackData (struktuur sündmuse töötleja ja selle jaoks kasutatava argumendi jaoks).
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 * osaliselt täidetud tüübi käitlemist * pointeriga. Me * deklareerime struktuuri * reactor, määratledes selle, takistades seeläbi kasutajat * seda otse muutmast. See on üks mustreid * andmete varjamise * , mis sobib hästi C semantikasse. reactor_deregister reactor.h reactor_reregister uuendavad huvipakkuvate sokettide nimekirja ja vastavaid sündmuste töötlejat * süsteemi selektoris ja häshtabelis.toob kaasa selle, et "saadetamise" hetkel server ei ole veel käivitatud ja kõik läheb katki. reactor.c Kuva registreerimisfunktsioonid bitimaski * genereeritud sündmustest ja kasutaja pointeri *
Funktsioonid Kuva reactor_run() funktsioon, Pange tähele, et oleme kasutanud mitme tasandi ja Pange tähele, et oleme kasutanud mitme tasandi Pange tähele, et oleme kasutanud mitme tasandi
Pange tähele, et oleme kasutanud mitme tasandi
#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;
}Pange tähele, et oleme kasutanud mitme tasandi fdPange tähele, et oleme kasutanud mitme tasandi fd, Pange tähele, et oleme kasutanud mitme tasandi void.
Pange tähele, et oleme kasutanud mitme tasandi
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) {
// Error
case -1:
perror("epoll_wait");
result = -1;
goto cleanup;
// Timeout
case 0:
result = 0;
goto cleanup;
// Successful operation
default:
// Invoke the event handlers
for (int i = 0; i table, &fd);
callback->callback(callback->arg, fd, events[i].events);
}
}
}
cleanup:
free(events);
return result;
}Kokkuvõtteks, kasutajakoodi funktsioonide ahel näeb välja järgmine:

Üksikprotsessoriga server
Selleks, et testida I/O reactorit suure koormuse korral, kirjutame lihtsa HTTP veebiserveri, mis vastab igale päringule pildiga.
Lühike ülevaade HTTP protokollist
— see on protokoll , mida kasutatakse peamiselt serveri ja brauseri vaheliseks suhtlemiseks.
HTTP-d on lihtne kasutada transpordikihi peal protokollist Päringu formaat .
CRLF CRLF CRLF CRLF CRLF
CRLF— on kahe tähe jadad:, mis eraldab päringu esimese rea, pealkirjad ja andmed.rjan, разделяющая первую строку запроса, заголовки и данные.— on üks järgmistestCONNECTOPTIONS,DELETE,GET,HEAD,TRACE.,PATCH,POST,PUT,Brauser saadab meie serverile käsu,, mis tähendab 'Saada mulle failisisu'.GET, означающую «Отправь мне содержимое файла».ühtne ressurssi identifitseerija.— , siis klient küsib saidi peamist lehte./index.html, то клиент запрашивает главную страницу сайта.— HTTP protokolli versioon formaadisHTTP/X.Y.Tänapäeval on kõige sagedamini kasutatav versioon —HTTP/1.1HTTP/1.1.— on paar võti-väärtus formaadis:, saadetud serverile edasiseks analüüsiks., отправляемая серверу для дальнейшего анализа.— andmed, mida server vajab operatsiooni täitmiseks. Tihti on see lihtsaltvõi mõni muu formaat. Vastuse formaat
CRLF CRLF CRLF CRLF CRLF
<ВЕРСИЯ HTTP> <КОД СТАТУСА> <ОПИСАНИЕ СТАТУСА>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ><КОД СТАТУСА>— see number, which represents the result of the operation. Our server will always return a status 200 (successful operation).<ОПИСАНИЕ СТАТУСА>— a string representation of the status code. For status code 200, it isOK.— on paar võti-väärtus formaadis— a header in the same format as in the request. We will return headersContent-Length(file size) andContent-Type: text/html(type of returned data).— andmed, mida server vajab operatsiooni täitmiseks. Tihti on see lihtsalt— the data requested by the user. In our case, this is the path to the image in .
Fail (single-threaded server) includes the file , which contains the following function prototypes:
Show the 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 defined fail(). The macro compares the expression value with 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)Funktsioon fail() prints the given 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);
}Funktsioon new_server() returns a file descriptor for the 'server' socket, created by system calls , ja and able to accept incoming connections in non-blocking mode.
Show the function 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;
}- Note that the socket is initially created in non-blocking mode with the flag
SOCK_NONBLOCK, so in the functionon_accept()(read more) the system callaccept()does not stop the execution of the thread. - Kui
reuse_portontrue, this function will configure the socket with the option through , to use the same port in a multithreaded environment (see the 'Multithreaded server' section).
The event handler on_accept() is called after the OS generates the event EPOLLIN, in this case meaning that a new connection can be accepted. on_accept() accepts a new connection, switches it to non-blocking mode, and registers it with the event handler on_recv() in the I/O reactor.
Show the function 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);
}The event handler on_recv() is called after the OS generates the event EPOLLIN, mis tähendab, et ühendus, mis on registreeritud on_accept(), on valmis andmeid vastu võtma.
on_recv() loeb andmeid ühendusest, kuni HTTP päring on täielikult saadud, seejärel registreerib see töötleja on_send() HTTP vastuse saatmiseks. Kui klient katkestab ühenduse, eemaldatakse sokket registreerimisest ja suletakse läbi .
Kuva funktsioon on_recv()
static void on_recv(void *arg, int fd, uint32_t events) {
RequestBuffer *buffer = arg;
// Vastuvõtame sissetulevaid andmeid seni, 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 ei olnud see viga, mis blokeeriks
// voogu
if (errno != EAGAIN && errno != EWOULDBLOCK) {
request_buffer_destroy(buffer);
fail("read");
}
// Saadi täielik HTTP päring 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);
}
}The event handler on_send() is called after the OS generates the event EPOLLOUT, mis tähendab, et ühendus, mis on registreeritud on_recv(), on valmis andmeid saatma. See funktsioon saadab HTTP vastuse, mis sisaldab HTML-i koos pildiga kliendile, ja seejärel muudab sündmuste töötlejat taas on_recv().
Kuva funktsioon on_send()
staatiline tüüp on_send(void *arg, int fd, uint32_t events) {
const char *sisu = "<img "
"src="https://habrastorage.org/webt/oh/wl/23/"
"ohwl23va3b-dioerobq_mbx4xaw.jpeg">";
char vastus[1024];
sprintf(vastus,
"HTTP/1.1 200 OK" CRLF "Content-Length: %zd" CRLF "Content-Type: "
"text/html" DOUBLE_CRLF "%s",
strlen(sisu), sisu);
SAFE_CALL(send(fd, vastus, strlen(vastus), 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 reaktori läbi reactor_new(), loome serveri soketi ja registreerime selle, käivitame reaktori läbi reactor_run() täpselt üheks minutiks, seejärel vabastame ressursid ja lahkume programmist.
Kuva 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 projektijuurest) ja käivitame isetehtud serveri, avame brauseris ja jälgime seda, mida ootasime:

Jõudluse mõõtmine
Kuva minu masina omadused
$ 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 Theme: Mint-Y-Dark (Mint-Y)
NMm dMM` ..` ... ydm. dMM GTK Theme: Mint-Y [GTK2/3]
hMM- +MMd/-------...-:sdds dMM Icon Theme: Mint-Y
-NMm- :hNMNNNmdddddddddy/` dMM Font: 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 ühekiiruselise serveri jõudlust. Avame kaks terminali: ühes käivitame ./http_server, teises — . Ühe minuti pärast kuvatakse teises terminalis järgmine statistika:
$ 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"
Running 1m test @ http://127.0.0.1:18470
8 threads and 100 connections
Thread Stats Avg Stdev Max +/ Stdev
Latency 493.52us 76.70us 17.31ms 89.57%
Req/Sec 24.37k 1.81k 29.34k 68.13%
11657769 requests in 1.00m, 1.60GB read
Requests/sec: 193974.70
Transfer/sec: 27.19MBMeie ühekiiruseline server suutis töödelda rohkem kui 11 miljonit päringut minutis, mis tulenevad 100 ühendusest. Tulemused on head, aga kas neid on võimalik parandada?
Mitme lõimega server
Kuidas mainitud, saab I/O reaktorit luua eraldi lõimedes, mistõttu kasutatakse kõiki CPU'sid. Rakendame seda lähenemist praktikas:
Näita 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 on igal lõimel reaktor:
static Reactor *reactor;
#pragma omp threadprivate(reactor)Pange tähele, et funktsiooni argumendiks new_server() seisab true. See tähendab, et määrame serveri soketile sätte , et kasutada seda mitme lõimega keskkonnas. Lisainfot leiate .
Teine katse
Nüüd mõõdame mitme lõimega 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"
Running 1m test @ http://127.0.0.1:18470
8 threads and 100 connections
Thread Stats Avg Stdev Max +/ Stdev
Latency 1.14ms 2.53ms 40.73ms 89.98%
Req/Sec 79.98k 18.07k 154.64k 78.65%
38208400 requests in 1.00m, 5.23GB read
Requests/sec: 635876.41
Transfer/sec: 89.14MBTöödeldud päringute arv 1 minuti jooksul suurenes ~3,28 korda! Kuid ringisummani jäi puudu vaid ~kaks miljonit, proovime seda parandada.
Esmalt vaatame statistikat, mille genereeris :
$ sudo perf stat -B -e task-clock,context-switches,cpu-migrations,page-faults,cycles,instructions,branches,branch-misses,cache-misses .\/http_server_multithreaded
Performance counter stats for '.\/http_server_multithreaded':
242446,314933 task-clock (msec) # 4,000 CPUs utilized
1 813 074 context-switches # 0,007 M\/sec
4 689 cpu-migrations # 0,019 K\/sec
254 page-faults # 0,001 K\/sec
895 324 830 170 cycles # 3,693 GHz
621 378 066 808 instructions # 0,69 insn per cycle
119 926 709 370 branches # 494,653 M\/sec
3 227 095 669 branch-misses # 2,69% of all branches
808 664 cache-misses
60,604330670 seconds time elapsed, kompileerimine lippudega -march=native, , tabamuste arvu suurendamine , suurendamine MAX_EVENTS ja kasutamine EPOLLET ei andnud märkimisväärset kasu jõudluses. Aga mis juhtub, kui suurendada samaaegsete ühenduste arvu?
Statistika 352 samaaegse ü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"
Running 1m test @ http:\/\/127.0.0.1:18470
8 threads and 352 connections
Thread Stats Avg Stdev Max +\/ - Stdev
Latency 2.12ms 3.79ms 68.23ms 87.49%
Req\/Sec 83.78k 12.69k 169.81k 83.59%
40006142 requests in 1.00m, 5.48GB read
Requests\/sec: 665789.26
Transfer\/sec: 93.34MBSoovitud tulemus on saavutatud ning koos sellega huvitav graafik, mis näitab, kuidas ühe minuti jooksul töödeldud päringute arv sõltub ühenduste arvust:

Nähes, et paari saja ühenduse järel langeb töödeldud päringute arv mõlema serveri puhul järsult (mitme sõlme variandi puhul on see rohkem märgatav). Kas see on seotud Linuxi TCP\/IP steki rakendusega? Ootame teie arvamusi selle graafiku käitumise ja mitme- ning ühetuus variationide optimeerimise kohta kommentaarides.
Kuidas kommentaarides, see jõudlustest ei näita I\/O reaktori käitumist reaalsetes koormustes, kuna server suhtleb peaaegu alati andmebaasiga, genereerib logisid, kasutab krüptograafiat jne., mistõttu koormus muutub ebaühtlaseks (dünaamiliseks). Testid koos väliste komponentidega viiakse läbi I\/O reaktori artiklis.
I\/O reaktori puudused
Peab mõistma, et I\/O reaktoril on puudusi, nimelt:
- I\/O reaktori kasutamine mitme niidi keskkonnas on veidi keerulisem, kuna tuleb käsitsi hallata niite.
- Praktika näitab, et enamikul juhtudel on koormus ebamugav, mis võib viia olukorrani, kus üks voog töötab, samas kui teine on tööga üle koormatud.
- Kui üks sündmuse töötleja lukustab voolu, siis lukustub ka süsteemi valija, mis võib viia raskesti leidlike vigadeni.
Need probleemid lahendab , millel on sageli ajakava, mis jaotab koormuse ühtlaselt lõime basseini, ja millel on ka mugavam API. Räägime sellest hiljem, minu teises artiklis.
Kokkuvõte
Selles seoses on meie teekond teooriast otse profilerist lõppenud.
Ei tasu sellega piirduda, sest on palju teisi sama huvitavaid lähenemisviise võrgutarkvara kirjutamiseks, millel on erinevad mugavuse ja kiirusetase. Minu arvates on allpool huvitavad lingid.
Kohtumiseni!
Huvitavad projektid
- C
Mida veel lugeda?
Allikas: habr.com
