Beste antwoord
Het antwoord op de gestelde vraag is: het is hetzelfde als het verschil tussen één “en” veel “.
Maar ik denk dat wat je waarschijnlijk echt bedoelde iets meer is in de vorm van: hoe verschillen programmeermethodologieën voor programmeren in een enkele thread in een enkel proces vergeleken met in meerdere threads in een enkele proces?
Er zijn boeken over het onderwerp geschreven. Maar heel kort, de verschillen hebben allemaal betrekking op één factor: bij het programmeren in één thread en proces, weet je altijd hoe je bij de huidige instructie bent gekomen. Instructies vinden plaats in een enkele reeks. Maar als je voor veel threads programmeert, heb je geen idee welke instructies in welke volgorde zijn uitgevoerd om bij de huidige instructie te komen. Dit maakt het programmeren ingewikkelder.
Maar er is nog een andere factor die het compliceert: in tegenstelling tot processen delen threads geheugen. Je weet niet welke thread het laatst een bepaalde geheugenlocatie heeft aangeraakt, of welke de volgende zal zijn, tenzij je een soort “synchronisatie” hebt. Vandaar het “gesynchroniseerde” sleutelwoord in Java (gescheurd van Ada). Monitoren, semaforen, vergrendelingen , conditievariabelen en zelfs het doorgeven van berichten zijn allemaal gebruikt voor synchronisatie.
Antwoord
(Update: pas op voor een ander antwoord dat zegt dat je geen single-threaded webserver kunt hebben die verwerkt gelijktijdige verzoeken goed, want dat is gewoon niet waar.)
Waarom is een multithreaded webserver beter dan een single thread-server? Dat is het niet.
Er zijn vier basismanieren waarop een webserver gelijktijdigheid kan verwerken:
- forking een OS-proces per verzoek (zoals oude versies van Apache)
- het spawnen van een OS-thread per verzoek (zoals een nieuwe versie van Apache)
- met behulp van een single-threaded event-loop (zoals nginx )
- met behulp van groene draden of lichtgewicht processen gepland door een VM-runtime in plaats van het besturingssysteem (zoals in Erlang)
Momenteel zijn de meest voorkomende benaderingen nummer 2 en 3.
Er zijn voor- en nadelen van beide van hen. Voor I / O-gebonden bewerkingen (een kenmerk van een typische webserver) krijgt u betere prestaties en hoger aantal gelijktijdige verzoeken wanneer u een single-threaded gebeurtenislus gebruikt. Maar het nadeel is dat u uitsluitend asynchrone niet-blokkerende I / O moet gebruiken voor alle bewerkingen, anders blokkeert u de gebeurtenislus en verliest u de prestaties. Om die reden is het gemakkelijker om een multi-threaded server te implementeren, maar u betaalt in prestatie.
Voor CPU-gebonden bewerkingen (minder gebruikelijk voor een gewone webserver, misschien gebruikelijker voor een rekenkundig intensieve API) is het het beste om één OS-thread of -proces per kern te hebben. Het is gemakkelijk te doen met gebeurtenisloops met één thread, omdat u een cluster van een aantal processen per kern kunt uitvoeren. Het is moeilijk te doen met multi-threaded servers, want als spawning-threads uw enige manier is om gelijktijdige verzoeken af te handelen, kunt u niet echt bepalen hoeveel threads u zult hebben – aangezien u geen controle heeft over het aantal verzoeken. Als je eenmaal meer threads hebt dan het aantal CPU-cores, verlies je prestaties voor contextschakelaars en gebruik je ook veel RAM.
Dat is de reden waarom een single-threaded nginx-server beter presteert dan een multi-threaded Apache-webserver (en daarom is nginx in de eerste plaats gemaakt). Ook Redis , een database die bekend staat om zijn uitzonderlijk hoge prestaties, is single-threaded .
Een echt voorbeeld dat ik je kan geven is dit: Mijn eerste webserver was Apache die draaide op een Linux-machine met 500 MB RAM. Het heeft voor elk verzoek een nieuw proces gevorkt (het had eigenlijk een pool, dus er was niet veel sprake van forking, maar het moest die processen levend houden om ze opnieuw te gebruiken en ze af en toe te doden om lekken van bronnen te voorkomen).
Mijn besturingssysteem gebruikte ongeveer 100 MB RAM. Elk Apache-proces gebruikte 20 MB RAM. Het betekende dat mijn server slechts 20 gelijktijdige verzoeken kon verwerken en er was geen manier omheen omdat ik geen RAM meer had. De processen waren meestal geblokkeerd op I / O, dus het CPU-gebruik was erg laag, elk verzoek boven die 20 moest wachten en als die 20 b.v. Bij langlopende downloads reageerde mijn server helemaal niet.
Toen de nginx-webserver werd geïntroduceerd, gebruikte deze een single-threaded event-loop en blokkeerde deze niet voor welk verzoek dan ook. Het kon veel meer gelijktijdige verzoeken aan, zonder problemen met het mythische c10k-probleem – nginx is in feite gemaakt om het c10k-probleem op te lossen (10.000 gelijktijdige verzoeken).
Stel je voor hoeveel RAM er wordt verspild voor 10.000 threads als je zou zelfs zoveel kunnen voortbrengen en hoeveel tijd wordt gebruikt voor contextwisselingen.
Geheugengebruik van multi-threaded Apache versus single-threaded nginx:
Dit is overigens de reden waarom Ryan Dahl een non-blocking I / O en een single-threaded event loop in Node.js gebruikte en hij nog steeds hetzelfde idee gebruikt in Deno, omdat dat de manier is om hoogwaardige netwerkservers te schrijven (in tegenstelling tot wat u in andere antwoorden hier zou kunnen lezen).