
Introducció
(un sol fil ) és un patró per escriure programari d'alta càrrega, utilitzat en moltes solucions populars:
- ...
En aquest article, veurem els detalls d'un reactor d'E/S i com funciona, escriurem una implementació en menys de 200 línies de codi i realitzarem un procés senzill de servidor HTTP de més de 40 milions de sol·licituds/min.
Prefaci
- L'article va ser escrit per ajudar a entendre el funcionament del reactor d'E/S i, per tant, entendre els riscos en utilitzar-lo.
- Es requereix coneixements bàsics per entendre l'article. i una mica d'experiència en el desenvolupament d'aplicacions de xarxa.
- Tot el codi està escrit en llenguatge C estrictament segons (precaució: PDF llarg) per a Linux i disponible a .
Per què fer-ho?
Amb la creixent popularitat d'Internet, els servidors web van començar a necessitar gestionar un gran nombre de connexions simultàniament i, per tant, es van provar dos enfocaments: bloquejar E/S en un gran nombre de fils del sistema operatiu i E/S no bloquejant en combinació amb un sistema de notificació d'esdeveniments, també anomenat "selector del sistema" (///etc).
El primer enfocament consistia a crear un nou fil del sistema operatiu per a cada connexió entrant. El seu inconvenient és la poca escalabilitat: el sistema operatiu n'haurà d'implementar molts и . Són operacions cares i poden provocar una manca de memòria RAM lliure amb un nombre impressionant de connexions.
La versió modificada destaca (agrupació de fils), evitant així que el sistema avorti l'execució, però al mateix temps introdueix un nou problema: si actualment un grup de fils està bloquejat per operacions de lectura llarga, llavors altres sòcols que ja poden rebre dades no podran Fes-ho.
El segon enfocament utilitza (selector del sistema) proporcionat pel sistema operatiu. Aquest article tracta el tipus més comú de selector del sistema, basat en alertes (esdeveniments, notificacions) sobre la preparació per a operacions d'E/S, en lloc de . Un exemple simplificat del seu ús es pot representar amb el següent diagrama de blocs:

La diferència entre aquests enfocaments és la següent:
- Bloqueig d'operacions d'E/S suspendre flux d'usuari fins quefins que el sistema operatiu estigui correctament entrant al flux de bytes (, rebent dades) o no hi haurà prou espai disponible als buffers d'escriptura interns per a l'enviament posterior mitjançant (enviament de dades).
- Selector del sistema al llarg del temps notifica al programa que el sistema operatiu ja paquets IP desfragmentats (TCP, recepció de dades) o espai suficient als buffers d'escriptura interns ja disponible (enviament de dades).
En resum, reservar un fil del sistema operatiu per a cada E/S és un malbaratament de potència de càlcul, perquè en realitat, els fils no fan feina útil (d'aquí el terme ). El selector del sistema resol aquest problema, permetent al programa d'usuari utilitzar els recursos de la CPU de manera molt més econòmica.
Model de reactor d'E/S
El reactor d'E/S actua com una capa entre el selector del sistema i el codi d'usuari. El principi del seu funcionament es descriu al següent diagrama de blocs:

- Permeteu-me que us recordi que un esdeveniment és una notificació que un determinat sòcol és capaç de realitzar una operació d'E/S sense bloqueig.
- Un controlador d'esdeveniments és una funció cridada pel reactor d'E/S quan es rep un esdeveniment, que després realitza una operació d'E/S sense bloqueig.
És important tenir en compte que el reactor d'E/S és, per definició, d'un sol fil, però no hi ha res que impedeixi que el concepte s'utilitzi en un entorn de múltiples fils amb una proporció d'1 fil: 1 reactor, reciclant així tots els nuclis de la CPU.
Implementació
Col·locarem la interfície pública en un fitxer , i implementació - en . reactor.h constarà dels següents anuncis:
Mostra declaracions al reactor.h
typedef struct reactor Reactor;
/*
* Указатель на функцию, которая будет вызываться I/O реактором при поступлении
* события от системного селектора.
*/
typedef void (*Callback)(void *arg, int fd, uint32_t events);
/*
* Возвращает `NULL` в случае ошибки, не-`NULL` указатель на `Reactor` в
* противном случае.
*/
Reactor *reactor_new(void);
/*
* Освобождает системный селектор, все зарегистрированные сокеты в данный момент
* времени и сам I/O реактор.
*
* Следующие функции возвращают -1 в случае ошибки, 0 в случае успеха.
*/
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);
/*
* Запускает цикл событий с тайм-аутом `timeout`.
*
* Эта функция передаст управление вызывающему коду если отведённое время вышло
* или/и при отсутствии зарегистрированных сокетов.
*/
int reactor_run(const Reactor *reactor, time_t timeout);L'estructura del reactor d'E/S consta de selector и , que assigna cada sòcol a CallbackData (estructura d'un controlador d'esdeveniments i un argument d'usuari per a aquest).
Mostra Reactor i CallbackData
struct reactor {
int epoll_fd;
GHashTable *table; // (int, CallbackData)
};
typedef struct {
Callback callback;
void *arg;
} CallbackData;Tingueu en compte que hem habilitat la capacitat de gestionar segons l'índex. EN reactor.h declarem l'estructura reactori en reactor.c el definim, evitant així que l'usuari canviï explícitament els seus camps. Aquest és un dels patrons , que encaixa succintament en la semàntica C.
Funcions reactor_register, reactor_deregister и reactor_reregister actualitzeu la llista de sockets d'interès i els controladors d'esdeveniments corresponents al selector del sistema i a la taula hash.
Mostra les funcions de registre
#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;
}Després que el reactor d'E/S hagi interceptat l'esdeveniment amb el descriptor fd, crida al controlador d'esdeveniments corresponent, al qual passa fd, esdeveniments generats i un punter d'usuari a void.
Mostra la funció 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) {
// Ошибка
case -1:
perror("epoll_wait");
result = -1;
goto cleanup;
// Время вышло
case 0:
result = 0;
goto cleanup;
// Успешная операция
default:
// Вызвать обработчиков событий
for (int i = 0; i < nfds; i++) {
int fd = events[i].data.fd;
CallbackData *callback =
g_hash_table_lookup(reactor->table, &fd);
callback->callback(callback->arg, fd, events[i].events);
}
}
}
cleanup:
free(events);
return result;
}En resum, la cadena de trucades de funcions al codi d'usuari tindrà la forma següent:

Servidor d'un sol fil
Per tal de provar el reactor d'E/S amb una càrrega elevada, escriurem un servidor web HTTP senzill que respongui a qualsevol sol·licitud amb una imatge.
Una referència ràpida al protocol HTTP
- Aquest és el protocol , utilitzat principalment per a la interacció servidor-navegador.
HTTP es pot utilitzar fàcilment protocol , enviant i rebent missatges en un format especificat .
Format de sol·licitud
<КОМАНДА> <URI> <ВЕРСИЯ HTTP>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ>CRLFés una seqüència de dos caràcters:rиn, separant la primera línia de la sol·licitud, les capçaleres i les dades.<КОМАНДА>- una deCONNECT,DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,TRACE. El navegador enviarà una ordre al nostre servidorGET, que significa "Envia'm el contingut del fitxer".<URI>- . Per exemple, si URI =/index.html, aleshores el client sol·licita la pàgina principal del lloc.<ВЕРСИЯ HTTP>— versió del protocol HTTP en el formatHTTP/X.Y. La versió més utilitzada avui ésHTTP/1.1.<ЗАГОЛОВОК N>és una parella clau-valor en el format<КЛЮЧ>: <ЗНАЧЕНИЕ>, enviat al servidor per a una anàlisi posterior.<ДАННЫЕ>— dades requerides pel servidor per realitzar l'operació. Sovint és senzill o qualsevol altre format.
Format de resposta
<ВЕРСИЯ HTTP> <КОД СТАТУСА> <ОПИСАНИЕ СТАТУСА>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ><КОД СТАТУСА>és un nombre que representa el resultat de l'operació. El nostre servidor sempre retornarà l'estat 200 (operació correcta).<ОПИСАНИЕ СТАТУСА>— representació en cadena del codi d'estat. Per al codi d'estat 200, això ésOK.<ЗАГОЛОВОК N>— capçalera del mateix format que a la sol·licitud. Tornarem els títolsContent-Length(mida del fitxer) iContent-Type: text/html(tipus de dades de retorn).<ДАННЫЕ>— dades sol·licitades per l'usuari. En el nostre cas, aquest és el camí cap a la imatge .
expedient (servidor d'un sol fil) inclou fitxer , que conté els prototips de funcions següents:
Mostra prototips de funcions en comú.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);També es descriu la macro funcional SAFE_CALL() i es defineix la funció fail(). La macro compara el valor de l'expressió amb l'error i, si la condició és certa, crida a la funció fail():
#define SAFE_CALL(call, error)
do {
if ((call) == error) {
fail("%s", #call);
}
} while (false)Funció fail() imprimeix els arguments passats al terminal (com ara ) i finalitza el programa amb el codi 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);
}Funció new_server() retorna el descriptor de fitxer del sòcol "servidor" creat per les trucades del sistema , и i capaç d'acceptar connexions entrants en un mode sense bloqueig.
Mostra la funció 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;
}- Tingueu en compte que el sòcol es crea inicialment en mode sense bloqueig mitjançant la bandera
SOCK_NONBLOCKde manera que en la funcióon_accept()(llegir més) trucada al sistemaaccept()no va aturar l'execució del fil. - Si
reuse_portés igual atrue, llavors aquesta funció configurarà el sòcol amb l'opció a través per utilitzar el mateix port en un entorn multifil (vegeu la secció "Servidor multifil").
Gestor d'esdeveniments on_accept() cridat després que el sistema operatiu generi un esdeveniment EPOLLIN, en aquest cas significa que es pot acceptar la nova connexió. on_accept() accepta una connexió nova, la canvia al mode sense bloqueig i es registra amb un gestor d'esdeveniments on_recv() en un reactor d'E/S.
Mostra la funció 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);
}Gestor d'esdeveniments on_recv() cridat després que el sistema operatiu generi un esdeveniment EPOLLIN, en aquest cas significa que la connexió registrada on_accept(), llest per rebre dades.
on_recv() llegeix dades de la connexió fins que la sol·licitud HTTP es rep completament i després registra un controlador on_send() per enviar una resposta HTTP. Si el client trenca la connexió, el sòcol es dona de baixa i es tanca utilitzant .
Mostra la funció on_recv()
static void on_recv(void *arg, int fd, uint32_t events) {
RequestBuffer *buffer = arg;
// Принимаем входные данные до тех пор, что recv возвратит 0 или ошибку
ssize_t nread;
while ((nread = recv(fd, buffer->data + buffer->size,
REQUEST_BUFFER_CAPACITY - buffer->size, 0)) > 0)
buffer->size += nread;
// Клиент оборвал соединение
if (nread == 0) {
SAFE_CALL(reactor_deregister(reactor, fd), -1);
SAFE_CALL(close(fd), -1);
request_buffer_destroy(buffer);
return;
}
// read вернул ошибку, отличную от ошибки, при которой вызов заблокирует
// поток
if (errno != EAGAIN && errno != EWOULDBLOCK) {
request_buffer_destroy(buffer);
fail("read");
}
// Получен полный HTTP запрос от клиента. Теперь регистрируем обработчика
// событий для отправки данных
if (request_buffer_is_complete(buffer)) {
request_buffer_clear(buffer);
SAFE_CALL(reactor_reregister(reactor, fd, EPOLLOUT, on_send, buffer),
-1);
}
}Gestor d'esdeveniments on_send() cridat després que el sistema operatiu generi un esdeveniment EPOLLOUT, el que significa que la connexió registrada on_recv(), llest per enviar dades. Aquesta funció envia una resposta HTTP que conté HTML amb una imatge al client i després torna a canviar el controlador d'esdeveniments on_recv().
Mostra la funció 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);
}I finalment, a l'arxiu http_server.c, en funció main() creem un reactor d'E/S utilitzant reactor_new(), creeu un sòcol de servidor i registreu-lo, engegueu el reactor fent servir reactor_run() durant exactament un minut, i després alliberem recursos i sortim del programa.
Mostra 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);
}Comprovem que tot funciona com s'esperava. Compilant (chmod a+x compile.sh && ./compile.sh a l'arrel del projecte) i inicieu el servidor escrit per si mateix, obre al navegador i mireu què esperàvem:

Mesurament del rendiment
Mostra les especificacions del meu cotxe
$ 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
.MMMMMMMMMMMMMMMMMMMMesurem el rendiment d'un servidor d'un sol fil. Obrim dos terminals: en un anirem executant ./http_server, d'una manera diferent - . Al cap d'un minut, es mostraran les estadístiques següents al segon terminal:
$ 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.19MBEl nostre servidor d'un sol fil va poder processar més d'11 milions de sol·licituds per minut procedents de 100 connexions. No és un mal resultat, però es pot millorar?
Servidor multifils
Com s'ha esmentat anteriorment, el reactor d'E/S es pot crear en fils separats, utilitzant així tots els nuclis de la CPU. Posem en pràctica aquest enfocament:
Mostra 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);
}
}Ara cada fil reactor:
static Reactor *reactor;
#pragma omp threadprivate(reactor)Tingueu en compte que l'argument de la funció new_server() favors true. Això vol dir que assignem l'opció al sòcol del servidor per utilitzar-lo en un entorn multifils. Podeu llegir més detalls .
Segona tirada
Ara mesurem el rendiment d'un servidor multiprocés:
$ 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.14MBEl nombre de sol·licituds processades en 1 minut ha augmentat ~3.28 vegades! Però només ens quedaven uns XNUMX milions per arribar al número rodó, així que intentem arreglar-ho.
Primer mirem les estadístiques generades :
$ 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, recopilació amb -march=native, , un augment del nombre de visites , augmentar MAX_EVENTS i ús EPOLLET no ha donat un augment significatiu del rendiment. Però què passa si augmenteu el nombre de connexions simultànies?
Estadístiques de 352 connexions simultànies:
$ 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.34MBEs va obtenir el resultat desitjat, i amb ell un interessant gràfic que mostra la dependència del nombre de peticions processades en 1 minut del nombre de connexions:

Veiem que després d'un parell de centenars de connexions, el nombre de sol·licituds processades per als dos servidors disminueix bruscament (a la versió multifils això es nota més). Està relacionat amb la implementació de la pila TCP/IP de Linux? No dubteu a escriure els vostres supòsits sobre aquest comportament del gràfic i les optimitzacions per a opcions de fils múltiples i d'un sol fil als comentaris.
Com als comentaris, aquesta prova de rendiment no mostra el comportament del reactor d'E/S sota càrregues reals, perquè gairebé sempre el servidor interactua amb la base de dades, genera registres, utilitza criptografia amb etc., com a conseqüència de la qual cosa la càrrega esdevé no uniforme (dinàmica). Les proves juntament amb components de tercers es realitzaran a l'article sobre el proactor d'E/S.
Inconvenients del reactor d'E/S
Heu d'entendre que el reactor d'E/S no té els seus inconvenients, a saber:
- L'ús d'un reactor d'E/S en un entorn multifil és una mica més difícil, perquè hauràs de gestionar manualment els fluxos.
- La pràctica demostra que, en la majoria dels casos, la càrrega no és uniforme, cosa que pot provocar que un fil registri mentre un altre estigui ocupat amb la feina.
- Si un gestor d'esdeveniments bloqueja un fil, el selector del sistema també es bloquejarà, cosa que pot provocar errors difícils de trobar.
Soluciona aquests problemes , que sovint té un programador que distribueix uniformement la càrrega a un conjunt de fils i també té una API més convenient. En parlarem més endavant, en el meu altre article.
Conclusió
Aquí és on s'ha acabat el nostre viatge des de la teoria fins a l'escapament del perfilador.
No us hauríeu de detenir en això, perquè hi ha molts altres enfocaments igualment interessants per escriure programari de xarxa amb diferents nivells de comoditat i velocitat. Interessant, al meu entendre, es donen enllaços a continuació.
Us tornem a veure
Projectes interessants
- Sí
Què més he de llegir?
Font: www.habr.com
