Plenus-featured bare-C I/O reactor

Plenus-featured bare-C I/O reactor

introduction

I / O reactor (Uno staminea res loop) Forma scribendi est summus programmatis onus, in multis solutionibus popularibus adhibitum:

In hoc articulo spectabimus ins et notas I/O reactoris et quomodo operatur, exsecutionem in minus quam CC lineas codicis scribe, et processum HTTP servo simplici per 200 decies petitiones/min efficiemus.

praefatio

  • Articulus scriptus est ad auxilium intellegendum operationem I/O reactoris, ideoque periculum intellige cum ea utens.
  • Articulus ad cognitionem elementorum intelligendam requiritur. C lingua et experientiam in retis applicationis progressionem.
  • Totum codicem in C lingua stricte secundum (cautio: long PDF) ad C11 vexillum pro Linux ac available on GitHub.

Quid faciam?

Crescente interreti favore, ministri telae magnum numerum nexuum eodem tempore tractare coeperunt, quapropter duo aditus temptati sunt: ​​I/O in magno numero filorum OS interclusio et non-I/O coniunctim cum notificatio systematis eventus, etiam vocatur "ratio electrix" (epoll/kqueue/IOCP/etc).

Primus aditus implicatus est novum OS linum condere pro unoquoque ineunte nexu. Eius incommodum scalabilitas pauper est: ratio operativa multos efficere debebit transitus context и ratio vocat. Operationes pretiosae sunt et ad penuriam liberi RAM cum infigo numero nexuum ducere possunt.

Mutationem versionem volutpat certum numerum staminum (filum piscinae), quo systema abortus exsecutionis impeditur, sed simul novam quaestionem introducit: si linum stagnum in praesenti operationibus lectitis impeditum est, deinde aliae bases quae iam datas accipere non poterunt. facere.

Secundus usus accessus res notitia ratio (ratio electrix) provisum est a OS. Articulus hic maxime commune genus systematis selectoris agit, secundum summis (eventis, notificationibus) de promptu erga I/O operationes, quam in Acta Vicimediorum Communium eorum complementum. Simplex exemplum usus sui per sequentis tabulae stipitem repraesentari potest:

Plenus-featured bare-C I/O reactor

Discrimen inter has aditus talis est;

  • Clausus EGO / O res suspendendi user fluxus donecusque ad OS est bene fragmenta advenientis IP facis ut amnis byte (TCP, notitia accepta) vel non erit satis spatii in internis scribere buffers ad subsequentem missionem via nIHIL (mitto notitia).
  • Ratio electrix trans tempus notificat progressio quod OS iam defragmented IP packets (TCP, data receptione) vel satis spatii in internis scribe buffers iam praesto (mitto notitia).

Ad summam, reservato filo OS pro singulis I/O computandi potestatem superfluum est, quia re vera stamina non faciunt opus utile (unde vocabulum "software interrupt"). Systema electrix hoc problema solvit, permittens programmate utentis opibus CPU multo magis oeconomice utendi.

I / O reactor exemplar

Hoc I/O reactor iacuit inter electorem systematis et codicem usoris iacuit. Principium operationis eius ex hoc schemate scandalo describitur:

Plenus-featured bare-C I/O reactor

  • Admoneam te rem notificationem esse quamdam nervum nervum non-I/O operationem praestare posse.
  • Eventus tracto munus est quod "I/O reactor" vocatur cum eventus recipitur, qui tunc operationem non interclusit I/O exercet.

Illud notandum est quod I/O reactor definitione simplicium liciatorum est, sed nihil prohibet conceptum ab usu in multi- bili ambitu ad rationem 1 sequelae: 1 reactor, inde redivivus omnes CPU coros.

Реализация

Publicum interface in lima ponemus reactor.het exsecutionem - in reactor.c. reactor.h constent sequentia nuntiata:

Ostende declarationes 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);

In I / o structuram reactor est file descriptor electrix epoll и Nullam tables GHashTable, Quod maps utraque singula CallbackData (structura eventus tracto et argumentum usoris pro eo).

Ostende Reactor et CallbackData

struct reactor {
    int epoll_fd;
    GHashTable *table; // (int, CallbackData)
};

typedef struct {
    Callback callback;
    void *arg;
} CallbackData;

Quaeso nota quod facultatem tractandi potuimus genus imperfectum iuxta indicem. IN' reactor.h declaramus structuram reactoret in reactor.c definimus, quo minus explicite mutando agros usor. Haec una exemplaria latebras dataquod in C semanticis succincte quadrat.

munera reactor_register, reactor_deregister и reactor_reregister renovare indicem amarum usuris et eventus respondentes tractatores in systemate selectoris et mensae detrahendae.

Monstra adnotatione munera

#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;
}

Post I/O reactor eventum intercepit cum descriptorem fdvocat tractorem eventum respondentem ad quem transit fd, frenum larva generatae certe et a user monstratorem void.

monstrare reactor_run() function

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;
}

Ut brevissime dicam, catena functionis in codice usoris vocato formam sequentem capiet:

Plenus-featured bare-C I/O reactor

Una server liciatum

Ut probemus I/O reactor sub alto onere, scribemus simplici HTTP servo interretiali quod cuilibet rogationi cum imagine respondet.

A velox ad HTTP protocol

HTTP - hoc est protocol applicationem gradu ", praesertim pro commercio servo-pasco.

HTTP potest facile usus est transportari protocol TCPMissis et acceptis nuntiis in forma certa specificatio.

Forma petitionem

<КОМАНДА> <URI> <ВЕРСИЯ HTTP>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ>

  • CRLF ordo est duorum ingenia; r и nprimam aciem rogationis, capitis et data.
  • <КОМАНДА> - unum CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Mandatum navigatoris nostro servo mittet GETid est "mitte mihi tabella contenta."
  • <URI> - uniformis resource identifier. Exempli gratia, si URI = /index.htmltum summa situs petit clientis pagina.
  • <ВЕРСИЯ HTTP> - versio HTTP protocollo in forma HTTP/X.Y. Plerumque adsuesco assuesco version est hodie HTTP/1.1.
  • <ЗАГОЛОВОК N> est clavem-valorem par in forma <КЛЮЧ>: <ЗНАЧЕНИЕ>missis ministris ad ulteriora analysis.
  • <ДАННЫЕ> - notitia requiritur a servo ad operationem exercendam. Saepe suus 'simplex JSON vel aliqua alia forma.

Forma responsio

<ВЕРСИЯ HTTP> <КОД СТАТУСА> <ОПИСАНИЕ СТАТУСА>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ>

  • <КОД СТАТУСА> est numerus effectus operationis repraesentans. Servus noster statum 200 semper reddet (prospera operatione).
  • <ОПИСАНИЕ СТАТУСА> - linea repraesentatio status code. Nam status code CC hoc OK.
  • <ЗАГОЛОВОК N> - header of the same format as in the request. Titulos reddemus Content-Length (Lima mole) et Content-Type: text/html (Reditus notitia genus).
  • <ДАННЫЕ> - data petita a utente. In nobis, haec via est ad imaginem HTML.

lima http_server.c (Uno servo filo perforat) includit file common.hquae sequuntur prototypa functionis continet:

Munus monstrare prototypa 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);

Etiam eget tortor dictum SAFE_CALL() et munus definitur fail(). tortor valorem locutionis cum errore comparat, et, si vera conditio est, munus vocat fail():

#define SAFE_CALL(call, error)                                                 
    do {                                                                       
        if ((call) == error) {                                                   
            fail("%s", #call);                                                 
        }                                                                      
    } while (false)

munus fail() Transierunt rationes ad procer terminum (sicut * printf()) Et cum codice progressio terminatur 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);
}

munus new_server() refert tabella descriptor "servo" nervum creatum a systema vocat socket(), bind() и listen() et capaces hospites advenientes in modo non-obturbant.

Monstrare new_server() function

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;
}

  • Nota quod nervus initio creatus est in modo non-obstructionis utendi vexillum SOCK_NONBLOCKut in munere on_accept() (Lege magis) ratio vocationis accept() nec cessavit stamina supplicium.
  • si reuse_port quod true, hoc munus nervum cum optione configurabit SO_REUSEPORT propter setsockopt()ut eodem portu utamur in ambitu multi-filato (vide sectionem "Multi-filam servientis").

Event Handler on_accept() vocatus OS generat eventus EPOLLINhoc in casu significatio novi nexus accipi potest. on_accept() novam connexionem accipit, eam permutat ad modum non-obturantis et registra cum eventu tracto on_recv() per I / O reactor.

Ostende on_accept() functionem

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);
}

Event Handler on_recv() vocatus OS generat eventus EPOLLINin casu significat nexum descripti on_accept()paratus ad recipiendum data.

on_recv() data ex nexu legit donec petitio HTTP omnino recipitur, deinde tracto registratur on_send() mittere HTTP responsio. Si client nexum frangit, nervus descriptus est et occlusus utens close().

Munus monstrare 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);
    }
}

Event Handler on_send() vocatus OS generat eventus EPOLLOUT, id est nexum descripserunt on_recv()paratus mittere data. Hoc munus mittit responsionem HTTP continentem HTML cum imagine clientis ac deinde eventum tracto ad tergum mutat on_recv().

Ostende on_send() function

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);
}

Et tandem in tabella http_server.cIn munus main() nos creare EGO / O reactor usura reactor_new(), servo nervum crea et subcriptio, reactorem utens incipe reactor_run() prorsus enim unum minutum, et tunc facultates emittimus et progressio exit.

Ostende 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);
}

Let's check that everything working as expected. Componendis (chmod a+x compile.sh && ./compile.sh in radice project) et sui ipsius scripta servo deducunt, open http://127.0.0.1:18470 in pasco et vide quid expectamus;

Plenus-featured bare-C I/O reactor

Euismod measurement

Ostende currus specifications

$ 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
             .MMMMMMMMMMMMMMMMMMM

Finem unius servi metiamur. Duo terminales aperiamus: in uno curremus ./http_serverAliter - wrk. Post minutum, sequentia statistica in secundo termino demonstrabitur:

$ 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.19MB

Unius-filaminis nostri servo processus per 11 decies centena petitiones per minutias ex 100 iunctis oriundis valuit. Non ex malo evenit, sed potest melius?

Multithreaded server

Ut supra, I/O reactor in staminibus separatis creari potest, adhibitis omnibus CPU nucleis. In praxim hanc accedamus;

Ostende 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);
    }
}

Nunc omne filum habet suum reactor:

static Reactor *reactor;
#pragma omp threadprivate(reactor)

Nota quod munus argumentum new_server() actus true. Id est quod optionem servo nervum assignamus SO_REUSEPORTuti in multi- plicata environment. Plura legere potes hic.

Secundo run

Nunc metiamur observantiam multi-filae servientis:

$ 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.14MB

Numerus petitionum discursum in 1 momento augetur ab ~3.28 temporibus! Sed tantum ~XNUMX milliones numeri rotundi deficiebant, sic illud statuere conemur.

Primum videamus mutant generatae perfectus:

$ 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

Uti CPU affinitas, compilationem cum -march=native, PGOaucto numero hits cache, augere MAX_EVENTS et utere EPOLLET notabile incrementum in perficientur non dedit. Sed quid fit si coniunctionum simul numerum augeas?

Statistics pro 352 nexus simultaneus;

$ 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.34MB

Optatus effectus consecutus est, et cum eo graphio interesting ostendens dependentiam numerorum petitionum discursuum in 1 minuto numero nexuum:

Plenus-featured bare-C I/O reactor

Videmus post duos centum nexus, numerum processuum petitionum utriusque servientium acriter (in versione multi-strato hoc magis notabile). Estne hoc cognatum Linux TCP/IP acervum exsecutionem? Liberum est tibi scribere principia tua de hac graphi moribus et optimizations pro multi-filatis et simplicibus sequelis in comment.

How attendendum in comment, haec probatio perficiendi mores I/O reactoris sub oneribus realibus non ostendit, quia fere semper ministrans cum datorum, outputorum tigna, cryptographia utitur cum TLS etc., ex quibus onus non uniforme fit. Proactor simul cum tertia factione perficietur in articulo de I/O proactor.

Incommoda EGO / O reactor

Intellegere debes me/O reactor non esse sine eius incommodis, scilicet:

  • Usus I/O reactor in ambitu multi-filato paulo difficilior est, quia manually fluxum habebis.
  • Exercitatio ostendit in pluribus onus non uniforme esse, quod ad unum colligationem ducere potest, dum alterum in opere occupatum est.
  • Si quis eventus tractator filum obstruit, ipsa ratio electrix etiam obstruet, quae cimices duris invenire potest.

Solvit has difficultates I / O proactorqui saepe schedulam habet, quae aequaliter ad piscinam staminum sarcinam distribuit, et API commodiorem habet. De eo postea dicemus, in alio articulo.

conclusio,

Inde est quod iter nostrum a theoria recta in profile exhaustum finem pervenit.

Hoc non est morandum, quia multae aliae sunt aeque interesting accessiones ad programmatum retis scribendi cum diversis gradibus commoditatis et velocitatis. Interest, ut opinor, nexus infra dantur.

Proximum tempus!

Interesting incepta

Quid aliud lego?

Source: www.habr.com

Add a comment