Peut-être que beaucoup d'amis trouvent cela un peu difficile d'apprendre NIO, et de nombreux concepts ne sont pas si clairs. Avant d'entrer dans la programmation Java NIO, discutons aujourd'hui de quelques connaissances de base : le modèle d'E/S. L'article suivant commence par les concepts de synchronisation et d'asynchrone, puis explique la différence entre les E/S bloquantes et non bloquantes, puis introduit la différence entre les E/S bloquantes et les E/S non bloquantes, puis introduit la différence entre les E/S synchrones et les E/S asynchrones, puis introduit 5 modèles IO, et présente enfin deux modèles de conception (Reactor et Proactor) liés à la conception IO hautes performances
Ce qui suit est le aperçu de la table des matières de cet article :
1. Qu'est-ce que synchronisation? Qu'est-ce que l'asynchrone ?
2. Qu'est-ce que le blocage ? Qu'est-ce que le non bloquant ?
3. Qu'est-ce qui bloque les IO ? Qu’est-ce que les E/S non bloquantes ?
4. Qu'est-ce que les E/S synchrones ? Qu’est-ce que les E/S asynchrones ?
5. Cinq modèles d'E/S
6. Deux modèles de conception d'E/S hautes performances
1. Qu'est-ce que la synchronisation ? Qu'est-ce que l'asynchrone ?
Les concepts de synchronisation et d'asynchronisme existent depuis longtemps, et il existe de nombreuses opinions sur la synchronisation et l'asynchronisme sur Internet. Voici ma compréhension personnelle :
La synchronisation signifie : si plusieurs tâches ou événements doivent se produire, ces tâches ou événements doivent être effectués un par un. L'exécution d'un événement ou d'une tâche entraînera l'ensemble du processus. attendre temporairement. Ces événements ne peuvent pas être exécutés simultanément ;
Asynchrone signifie : si plusieurs tâches ou événements se produisent, ces événements peuvent être exécutés simultanément et l'exécution d'un événement ou d'une tâche ne fera pas attendre l'ensemble du processus. temporairement.
C'est synchrone et asynchrone. Pour donner un exemple simple, s'il existe une tâche qui comprend deux sous-tâches A et B. Pour la synchronisation, lorsque A est en cours d'exécution, B ne peut qu'attendre que A soit terminé, puis B peut s'exécuter de manière asynchrone, A et B peuvent être exécutés ; exécuté simultanément, et B n'a pas à attendre que A termine son exécution, de sorte que l'exécution de A ne fera pas attendre temporairement la tâche entière.
Si vous ne comprenez toujours pas, vous pouvez d'abord lire les deux morceaux de code suivants :
void fun1() { } void fun2() { } void function(){ fun1(); fun2() ..... ..... }
Ce morceau de code est une synchronisation typique dans la fonction de méthode, fun1 provoquera une synchronisation ultérieure. fun2 ne peut pas être exécuté, fun2 doit attendre que fun1 termine son exécution avant de pouvoir être exécuté.
Ensuite, regardez le code suivant :
void fun1() { } void fun2() { } void function(){ new Thread(){ public void run() { fun1(); } }.start(); new Thread(){ public void run() { fun2(); } }.start(); ..... ..... }
Ce code est typiquement asynchrone. L'exécution de fun1 n'affectera pas l'exécution de fun2, et l'exécution de fun1 et fun2 ne provoquera pas son. le processus d'exécution ultérieur est temporairement en attente.
En fait, la synchronisation et l'asynchronisme sont des concepts très larges, et leur objectif réside dans la question de savoir si l'occurrence ou l'exécution d'un événement entraînera une attente temporaire de l'ensemble du processus lorsque plusieurs tâches et événements se produisent . Je pense qu'une analogie peut être faite entre synchronisation et asynchronisme avec le mot-clé synchronisé en Java. Lorsque plusieurs threads accèdent à une variable en même temps, l'accès de chaque thread à la variable est un événement. Pour la synchronisation, ces threads doivent accéder à la variable un par un pendant qu'un thread accède à la variable, les autres threads doivent attendre ; , plusieurs threads n'ont pas besoin d'accéder à la variable un par un, mais peuvent y accéder en même temps.
Par conséquent, je pense personnellement que la synchronisation et l'asynchronisme peuvent s'exprimer de plusieurs manières, mais la clé à retenir est que lorsque plusieurs tâches et événements se produisent, que l'occurrence ou l'exécution d'un événement entraîne l'ensemble du processus. attendez temporairement. De manière générale, l'asynchrone peut être obtenu grâce au multi-threading, mais n'oubliez pas de ne pas assimiler le multi-threading à l'asynchrone. L'asynchrone n'est qu'un modèle de macro, et l'utilisation du multi-threading pour obtenir l'asynchrone n'est qu'un moyen, et l'implémentation asynchrone peut également être réalisée. grâce au multi-traitement.
2. Qu'est-ce que le blocage ? Qu'est-ce que le non bloquant ?
La différence entre la synchronisation et l'asynchronisme a été introduite plus tôt dans cette section, examinons la différence entre le blocage et le non-blocage.
Le blocage est : lorsqu'un événement ou une tâche est en cours d'exécution, il émet une opération de requête, mais comme les conditions requises pour l'opération de requête ne sont pas remplies, il attendra toujours là, jusqu'à ce que les conditions soient remplies ;
Moyens non bloquants : lorsqu'un événement ou une tâche est en cours d'exécution, il émet une opération de requête si les conditions requises pour l'opération de requête ne sont pas remplies, il renvoie immédiatement un message d'indicateur pour informer que la condition n'est pas remplie et n'attendra pas indéfiniment.
C'est la différence entre le blocage et le non-blocage. C'est-à-dire que la principale différence entre le blocage et le non-blocage est lors de la demande d'une opération, si les conditions ne sont pas remplies, si elle attendra indéfiniment ou renverra un message indicateur .
Donnez un exemple simple :
Si je veux lire le contenu d'un fichier, s'il n'y a pas de contenu à lire dans le fichier à cet instant, pour la synchronisation, il attendra toujours là . Jusqu'à ce qu'il y ait du contenu lisible dans le fichier ; pour le non-blocage, un message d'indicateur sera renvoyé directement pour informer qu'il n'y a pas de contenu lisible dans le fichier.
Certains amis sur Internet assimilent respectivement la synchronisation et l'asynchronisme au blocage et au non-blocage. En fait, ce sont deux concepts complètement différents. Notez que comprendre la différence entre ces deux ensembles de concepts est très important pour comprendre le modèle IO ultérieur.
L'objectif de la synchronisation et de l'asynchronisme est de savoir si l'exécution d'une tâche entraînera une attente temporaire de l'ensemble du processus pendant l'exécution de plusieurs tâches
L'objectif du blocage et du non-blocage est ; lors de l'émission d'une demande Pendant l'exploitation, si les conditions de l'opération ne sont pas remplies, si un message indicateur sera renvoyé pour informer que les conditions ne sont pas remplies.
Comprendre le blocage et le non-blocage peut être compris par analogie avec le blocage de threads Lorsqu'un thread effectue une opération de requête, si la condition n'est pas remplie, il sera bloqué, c'est-à-dire qu'il attendra que la condition soit remplie. rencontré.
3. Qu'est-ce qui bloque les E/S ? Qu'est-ce que les E/S non bloquantes ?
Comprendre les E/S non bloquantes -blocking IO Avant de bloquer les IO, regardez d'abord comment le prochain processus d'opération IO spécifique est effectué.
De manière générale, les opérations d'E/S comprennent : la lecture et l'écriture sur le disque dur, la lecture et l'écriture sur le socket, et la lecture et l'écriture sur les périphériques.
Lorsqu'un thread utilisateur initie une opération de requête IO (cet article prend une opération de requête de lecture comme exemple), le noyau vérifiera si les données à lire sont prêtes pour bloquer les IO, si les données ne le sont pas. prêt, il continuera à attendre jusqu'à ce que les données soient prêtes ; pour les IO non bloquantes, si les données ne sont pas prêtes, un message d'indicateur sera renvoyé pour informer le thread utilisateur que les données en cours de lecture ne sont pas prêtes. Lorsque les données sont prêtes, les données sont copiées dans le thread utilisateur, de sorte qu'une opération complète de demande de lecture IO soit terminée, c'est-à-dire qu'une opération complète de demande de lecture IO comprend deux étapes :
1) Afficher. les données Est-ce prêt ?
2) Copier les données (le noyau copie les données dans le thread utilisateur).
Ensuite, la différence entre le blocage (IO bloquant) et le non bloquant (IO non bloquant) réside dans la première étape. Si les données ne sont pas prêtes, devez-vous continuer à attendre en vérifiant si les données sont prêtes, ou devriez-vous revenir directement ? Un message de drapeau.
L'IO traditionnelle en Java bloque l'IO, comme la lecture de données via le socket. Après avoir appelé la méthode read(), si les données ne sont pas prêtes, le thread actuel sera bloqué lors de l'appel de la méthode read jusqu'à ce que cela se produise. est des données et s'il s'agit d'E/S non bloquantes, lorsque les données ne sont pas prêtes, la méthode read() doit renvoyer un message d'indicateur pour informer le thread actuel que les données ne sont pas prêtes, au lieu d'y attendre tout le temps.
4. Qu'est-ce que les E/S synchrones ?
Jetons d'abord un coup d'œil aux définitions des IO synchrones et des IO asynchrones. Les définitions des IO synchrones et des IO asynchrones dans le livre "Programmation réseau Unix" sont les suivantes :
Une opération d'E/S synchrone entraîne le blocage du processus demandeur jusqu'à ce que cette opération d'E/S soit terminée.<code>A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes.<br> An asynchronous I/O operation does not cause the requesting process to be blocked.
Une opération d'E/S asynchrone n'entraîne pas le blocage du processus demandeur.
Cela peut être vu au sens littéral : IO synchrone signifie que si un thread demande une opération IO, le thread sera bloqué avant que l'opération IO ne soit terminée
IO asynchrone signifie que si ; un thread demande d'effectuer des opérations d'E/S, ce qui n'entraînera pas le blocage du thread de requête.
En fait, les modèles IO synchrones et IO asynchrones sont destinés à l'interaction entre les threads utilisateur et le noyau :
Pour les IO synchrones : après que l'utilisateur a émis une opération de requête IO, si les données sont pas prêt, Il est nécessaire de vérifier en permanence si les données sont prêtes via le thread utilisateur ou le noyau. Lorsque les données sont prêtes, les données sont copiées du noyau vers le thread utilisateur
IO asynchrone : uniquement. l'opération de demande d'E/S est émise par le thread utilisateur. Les deux étapes de l'opération d'E/S sont automatiquement complétées par le noyau, puis une notification est envoyée pour informer le thread utilisateur que l'opération d'E/S est terminée. C’est-à-dire que dans les IO asynchrones, il n’y aura pas de blocage des threads utilisateur.
C'est la principale différence entre les IO synchrones et les IO asynchrones. La différence clé entre les IO synchrones et les IO asynchrones se reflète dans le fait que la phase de copie des données est terminée par le thread utilisateur ou le noyau. Par conséquent, les E/S asynchrones doivent bénéficier d'un support sous-jacent du système d'exploitation.
Notez que les IO synchrones et les IO asynchrones sont deux concepts différents des IO bloquantes et des IO non bloquantes.
Les IO bloquantes et les IO non bloquantes se reflètent dans le fait que lorsque l'utilisateur demande une opération IO, si les données ne sont pas prêtes, le thread utilisateur recevra toujours un message d'indicateur s'il attend que les données soient envoyées. soyez prêt. En d'autres termes, les E/S bloquantes et les E/S non bloquantes se reflètent dans la première étape de l'opération d'E/S, comment elles sont traitées lors de la vérification si les données sont prêtes.
Cinq. 5 modèles d'E/S
Cinq modèles d'E/S sont mentionnés respectivement dans le livre "Unix Network Programming" : E/S bloquantes, E/S non bloquantes, E/S multiplexées, E/S pilotées par signal et E/S asynchrones.
Présentons respectivement les similitudes et les différences de ces 5 modèles IO.
1. Modèle IO de blocage
Le modèle IO le plus traditionnel, c'est-à-dire que le blocage se produit pendant le processus de lecture et d'écriture des données.
当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除block状态。
典型的阻塞IO模型的例子为:
data = socket.read();
如果数据没有就绪,就会一直阻塞在read方法。
2.非阻塞IO模型
当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。
所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。
典型的非阻塞IO模型一般如下:
while(true){ data = socket.read(); if(data!= error){ 处理数据 break; } }
但是对于非阻塞IO就有一个非常严重的问题,在while循环中需要不断地去询问内核数据是否就绪,这样会导致CPU占用率非常高,因此一般情况下很少使用while循环这种方式来读取数据。
3.多路复用IO模型
多路复用IO模型是目前使用得比较多的模型。Java NIO实际上就是多路复用IO。
在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。
在Java NIO中,是通过selector.select()去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这种方式会导致用户线程的阻塞。
也许有朋友会说,我可以采用 多线程+ 阻塞IO 达到类似的效果,但是由于在多线程 + 阻塞IO 中,每个socket对应一个线程,这样会造成很大的资源占用,并且尤其是对于长连接来说,线程的资源一直不会释放,如果后面陆续有很多连接的话,就会造成性能上的瓶颈。
而多路复用IO模式,通过一个线程就可以管理多个socket,只有当socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO比较适合连接数比较多的情况。
另外多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中,不断地询问socket状态时通过用户线程去进行的,而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。
不过要注意的是,多路复用IO模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件逐一进行响应。因此对于多路复用IO模型来说,一旦事件响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。
4.信号驱动IO模型
在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。
5.异步IO模型
异步IO模型才是最理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。
En d'autres termes, dans le modèle IO asynchrone, aucune des phases de l'opération IO ne bloquera le thread utilisateur. Les deux phases sont automatiquement terminées par le noyau, puis un signal est envoyé pour informer le thread utilisateur que l'opération est terminée. . Il n'est pas nécessaire d'appeler à nouveau la fonction IO dans le thread utilisateur pour une lecture et une écriture spécifiques. Ceci est différent du modèle basé sur le signal, lorsque le thread utilisateur reçoit le signal, cela indique que les données sont prêtes, puis le thread utilisateur doit appeler la fonction IO pour effectuer la lecture et la lecture réelles. opérations d'écriture ; dans le modèle IO asynchrone, la réception du signal indique que l'opération IO est terminée et qu'il n'est pas nécessaire d'appeler la fonction IO dans le thread utilisateur pour les opérations de lecture et d'écriture réelles.
Notez que les E/S asynchrones nécessitent une prise en charge sous-jacente de la part du système d'exploitation. Dans Java 7, les E/S asynchrones sont fournies.
Les quatre premiers modèles d'E/S sont en fait des E/S synchrones, et seul le dernier est une E/S véritablement asynchrone, car qu'il s'agisse d'E/S multiplexées ou d'un modèle piloté par signal, le deuxième de l'opération IO Chaque étape entraînera le blocage des threads utilisateur, c'est-à-dire que le processus de copie des données par le noyau entraînera le blocage des threads utilisateur.
6. Deux modèles de conception d'E/S hautes performances
Parmi les modèles de conception de services réseau traditionnels, il existe deux modèles classiques :
L'un est à plusieurs threads, l'autre est un pool de threads.
Pour le mode multi-thread, c'est-à-dire que lorsque le client arrive, le serveur créera un nouveau thread pour gérer les événements de lecture et d'écriture du client, comme le montre la figure ci-dessous :
Bien que ce mode soit simple et pratique à gérer, car le serveur utilise un thread pour traiter chaque connexion client, il consomme beaucoup de ressources. Par conséquent, lorsque le nombre de connexions atteint la limite supérieure et qu'un autre utilisateur demande une connexion, cela provoquera directement un goulot d'étranglement des ressources et, dans les cas graves, cela peut directement provoquer une panne du serveur.
Par conséquent, afin de résoudre les problèmes causés par le modèle un thread, un client, la méthode du pool de threads est proposée, ce qui signifie créer un pool de threads de taille fixe et créer un client Le thread. pool prend un thread inactif pour le traitement. Lorsque le client termine les opérations de lecture et d'écriture, il cède l'occupation du thread. Par conséquent, cela évite le gaspillage de ressources provoqué par la création de threads pour chaque client, afin que les threads puissent être réutilisés.
Mais le pool de threads a aussi ses inconvénients. Si la plupart des connexions sont des connexions longues, cela peut entraîner l'occupation de tous les threads du pool de threads pendant un certain temps, puis lorsqu'un autre utilisateur demande une connexion. , car il n'y a pas de Si les threads inactifs disponibles sont utilisés pour le traitement, la connexion client échouera, affectant ainsi l'expérience utilisateur. Par conséquent, le pool de threads est plus adapté à un grand nombre d’applications de connexion courte.
Par conséquent, les deux modèles de conception d'E/S hautes performances suivants ont émergé : Reactor
et Proactor
.
En mode Reactor, les événements d'intérêt seront d'abord enregistrés pour chaque client, puis un thread interrogera chaque client pour voir si un événement se produit. Lorsqu'un événement se produit, chaque client sera traité séquentiellement. lorsque tous les événements seront traités, ils seront transférés pour continuer l'interrogation, comme le montre la figure suivante :
Comme on peut le voir d'ici, parmi les cinq modèles IO ci-dessus les E/S multiplexées utilisent le mode Reactor. Notez que la figure ci-dessus montre que chaque événement est traité séquentiellement. Bien entendu, afin d'améliorer la vitesse de traitement des événements, les événements peuvent être traités via plusieurs threads ou pools de threads.
En mode Proactor, lorsqu'un événement est détecté, une nouvelle opération asynchrone sera lancée puis transmise au thread du noyau pour traitement. Lorsque le thread du noyau terminera l'opération IO, une notification sera envoyée pour informer. que l'opération est terminée. On peut savoir que le modèle IO asynchrone utilise le mode Proactor.
Veuillez me pardonner s'il y a des erreurs dans le contenu ci-dessus et accueillez vos critiques et corrections !
Pour plus de contenu connexe, veuillez visiter le site Web PHP chinois : Tutoriel vidéo JAVA
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!