Apakah I / O non-pemblokiran benar-benar lebih cepat daripada I / O pemblokiran multi-threaded? Bagaimana?


119

Saya menelusuri web pada beberapa detail teknis tentang memblokir I / O dan non memblokir I / O dan saya menemukan beberapa orang yang menyatakan bahwa non-pemblokiran I / O akan lebih cepat daripada memblokir I / O. Misalnya dalam dokumen ini .

Jika saya menggunakan pemblokiran I / O, maka tentu saja thread yang saat ini diblokir tidak dapat melakukan apa-apa lagi ... Karena sudah diblokir. Tetapi begitu utas mulai diblokir, OS dapat beralih ke utas lain dan tidak beralih kembali hingga ada sesuatu yang harus dilakukan untuk utas yang diblokir. Jadi selama ada utas lain pada sistem yang membutuhkan CPU dan tidak diblokir, seharusnya tidak ada lagi waktu idle CPU dibandingkan dengan pendekatan non-pemblokiran berbasis peristiwa, bukan?

Selain mengurangi waktu CPU dalam keadaan idle, saya melihat satu opsi lagi untuk meningkatkan jumlah tugas yang dapat dilakukan komputer dalam kerangka waktu tertentu: Kurangi overhead yang diperkenalkan dengan mengganti utas. Tapi bagaimana ini bisa dilakukan? Dan apakah biaya overhead cukup besar untuk menunjukkan efek yang dapat diukur? Berikut ini ide tentang bagaimana saya bisa membayangkannya bekerja:

  1. Untuk memuat konten file, aplikasi mendelegasikan tugas ini ke kerangka kerja i / o berbasis peristiwa, meneruskan fungsi panggilan balik bersama dengan nama file
  2. Kerangka acara mendelegasikan ke sistem operasi, yang memprogram pengontrol DMA dari hard disk untuk menulis file secara langsung ke memori
  3. Kerangka acara memungkinkan kode lebih lanjut untuk dijalankan.
  4. Setelah menyelesaikan penyalinan disk-ke-memori, pengontrol DMA menyebabkan interupsi.
  5. Penangan interupsi sistem operasi memberi tahu kerangka kerja i / o berbasis peristiwa tentang file yang dimuat sepenuhnya ke dalam memori. Bagaimana cara melakukannya? Menggunakan sinyal ??
  6. Kode yang saat ini dijalankan dalam event i / o framework selesai.
  7. Kerangka kerja i / o berbasis peristiwa memeriksa antriannya dan melihat pesan sistem operasi dari langkah 5 dan menjalankan callback yang didapatnya di langkah 1.

Apakah itu cara kerjanya? Jika tidak, bagaimana cara kerjanya? Itu berarti bahwa sistem acara dapat bekerja tanpa perlu menyentuh tumpukan secara eksplisit (seperti penjadwal nyata yang perlu mencadangkan tumpukan dan menyalin tumpukan utas lain ke dalam memori saat beralih utas)? Berapa banyak waktu yang sebenarnya dihemat? Apakah ada lebih dari itu?


5
jawaban singkatnya: ini lebih tentang overhead memiliki utas per koneksi. io non-pemblokiran memungkinkan seseorang menghindari utas per koneksi.
Dan D.

10
Memblokir IO mahal pada sistem di mana Anda tidak dapat membuat thread sebanyak koneksi yang ada. Di JVM Anda dapat membuat beberapa ribu utas, tetapi bagaimana jika Anda memiliki lebih dari 100.000 koneksi? Jadi, Anda harus tetap menggunakan solusi asinkron. Namun, ada bahasa di mana utas tidak mahal (mis. Utas hijau) seperti di Go / Erlang / Rust di mana tidak masalah memiliki 100.000 utas. Ketika jumlah utas bisa besar, saya yakin memblokir IO menghasilkan waktu respons yang lebih cepat. Tapi itu adalah sesuatu yang juga harus saya tanyakan kepada para ahli apakah itu benar dalam kenyataan.
OlliP

@OliverPlow, saya rasa begitu juga, karena memblokir IO biasanya berarti kita membiarkan sistem menangani "manajemen paralel", alih-alih melakukannya sendiri menggunakan antrian tugas dan semacamnya.
Pacerier

1
@DanD., Dan bagaimana jika overhead dari memiliki thread sama dengan overhead performaing non-blocking IO? (biasanya benar dalam kasus utas hijau)
Pacerier

"menyalin tumpukan" tidak terjadi. Untaian yang berbeda memiliki tumpukan di alamat yang berbeda. Setiap utas memiliki penunjuk tumpukan sendiri, bersama dengan register lainnya. Sakelar konteks hanya menyimpan / memulihkan status arsitektur (termasuk semua register), tetapi tidak memori. Di antara utas dalam proses yang sama, kernel bahkan tidak perlu mengubah tabel halaman.
Peter Cordes

Jawaban:


44

Keuntungan terbesar dari nonblocking atau asynchronous I / O adalah thread Anda dapat melanjutkan pekerjaannya secara paralel. Tentu saja Anda dapat mencapai ini juga menggunakan utas tambahan. Seperti yang Anda nyatakan untuk kinerja keseluruhan (sistem) terbaik, saya kira akan lebih baik menggunakan I / O asinkron dan bukan beberapa utas (jadi mengurangi peralihan utas).

Mari kita lihat kemungkinan implementasi program server jaringan yang akan menangani 1000 klien yang terhubung secara paralel:

  1. Satu utas per koneksi (dapat memblokir I / O, tetapi juga dapat menjadi I / O non-pemblokiran).
    Setiap utas membutuhkan sumber daya memori (juga memori kernel!), Itu merupakan kerugian. Dan setiap utas tambahan berarti lebih banyak pekerjaan untuk penjadwal.
  2. Satu utas untuk semua koneksi.
    Ini mengambil beban dari sistem karena kami memiliki lebih sedikit utas. Tetapi itu juga mencegah Anda menggunakan kinerja penuh mesin Anda, karena Anda mungkin akhirnya menggerakkan satu prosesor hingga 100% dan membiarkan semua prosesor lain menganggur.
  3. Beberapa utas tempat setiap utas menangani beberapa koneksi.
    Ini mengambil beban dari sistem karena jumlah utasnya lebih sedikit. Dan itu bisa menggunakan semua prosesor yang tersedia. Pada Windows, pendekatan ini didukung oleh Thread Pool API .

Tentu saja memiliki lebih banyak utas bukanlah masalah. Seperti yang mungkin sudah Anda ketahui, saya memilih jumlah koneksi / utas yang cukup tinggi. Saya ragu Anda akan melihat perbedaan antara tiga kemungkinan implementasi jika kita berbicara tentang hanya selusin utas (ini juga yang disarankan Raymond Chen di posting blog MSDN Apakah Windows memiliki batas 2000 utas per proses? ).

Pada Windows menggunakan file I / O unbuffered berarti bahwa menulis harus dari ukuran yang merupakan kelipatan dari ukuran halaman. Saya belum mengujinya, tetapi sepertinya ini juga dapat memengaruhi kinerja tulis secara positif untuk penulisan buffer sinkron dan asinkron.

Langkah 1 hingga 7 yang Anda jelaskan memberikan gambaran yang bagus tentang cara kerjanya. Di Windows, sistem operasi akan memberi tahu Anda tentang penyelesaian I / O asinkron ( WriteFiledengan OVERLAPPEDstruktur) menggunakan peristiwa atau callback. Fungsi panggilan balik hanya akan dipanggil misalnya ketika kode Anda memanggil WaitForMultipleObjectsExdengan bAlertabledisetel ke true.

Beberapa bacaan lagi di web:


Dari sudut pandang web, pengetahuan umum (Internet, komentar dari para ahli) menunjukkan bahwa sangat meningkatkan maks. jumlah utas permintaan adalah hal yang buruk dalam memblokir IO (membuat pemrosesan permintaan lebih lambat) karena peningkatan memori dan waktu peralihan konteks, tetapi, bukankah Async IO melakukan hal yang sama ketika menunda pekerjaan ke utas lain? Ya, Anda dapat melayani lebih banyak permintaan sekarang tetapi memiliki jumlah untaian yang sama di latar belakang .. apa manfaat sebenarnya dari itu?
JavierJ

1
@JavierJ Anda sepertinya percaya bahwa jika n utas melakukan asinkron file IO n utas lain akan dibuat untuk melakukan IO file pemblokiran? Ini tidak benar. OS memiliki dukungan IO file asinkron dan tidak perlu diblokir saat menunggu IO selesai. Ia dapat mengantrekan permintaan IO dan jika terjadi interupsi perangkat keras (misalnya DMA), ia dapat menandai permintaan tersebut sebagai selesai dan mengatur sebuah peristiwa yang menandakan thread pemanggil. Bahkan jika utas tambahan diperlukan, OS akan dapat menggunakan utas itu untuk beberapa permintaan IO dari beberapa utas.
Werner Henze

Terima kasih, masuk akal jika melibatkan dukungan IO file async OS tetapi ketika saya menulis kode untuk implementasi sebenarnya dari ini (dari sudut pandang web) katakanlah dengan Java Servlet 3.0 NIO saya masih melihat utas untuk permintaan dan utas latar belakang ( async) perulangan untuk membaca file, database, atau apa pun.
JavierJ

1
@piyushGoyal Saya menulis ulang jawaban saya. Saya harap sekarang lebih jelas.
Werner Henze

1
Pada Windows menggunakan file asynchronous I / O berarti bahwa menulis harus dalam ukuran yang merupakan kelipatan dari ukuran halaman. - tidak, tidak. Anda sedang memikirkan I / O tanpa buffer. (Mereka sering digunakan bersama-sama, tetapi tidak harus.)
Harry Johnston

29

I / O mencakup berbagai jenis operasi seperti membaca dan menulis data dari hard drive, mengakses sumber daya jaringan, menelepon layanan web, atau mengambil data dari database. Bergantung pada platform dan jenis operasinya, I / O asinkron biasanya akan memanfaatkan perangkat keras apa pun atau dukungan sistem tingkat rendah untuk melakukan operasi. Ini berarti bahwa itu akan dilakukan dengan dampak sekecil mungkin pada CPU.

Pada tingkat aplikasi, asinkron I / O mencegah thread menunggu operasi I / O selesai. Segera setelah operasi I / O asinkron dimulai, ini akan melepaskan utas tempat peluncurannya dan callback terdaftar. Saat operasi selesai, callback diantrekan untuk dieksekusi pada thread pertama yang tersedia.

Jika operasi I / O dijalankan secara sinkron, maka thread yang sedang berjalan tidak melakukan apa pun hingga operasi selesai. Runtime tidak tahu kapan operasi I / O selesai, sehingga secara berkala akan menyediakan beberapa waktu CPU ke thread menunggu, waktu CPU yang seharusnya dapat digunakan oleh thread lain yang memiliki operasi terikat CPU yang sebenarnya untuk dijalankan.

Jadi, seperti yang disebutkan @ user1629468, asynchronous I / O tidak memberikan kinerja yang lebih baik melainkan skalabilitas yang lebih baik. Ini terlihat jelas saat berjalan dalam konteks yang memiliki jumlah untaian yang terbatas, seperti halnya dengan aplikasi web. Aplikasi web biasanya menggunakan kumpulan utas tempat mereka menetapkan utas untuk setiap permintaan. Jika permintaan diblokir pada operasi I / O yang berjalan lama, ada risiko menghabiskan kumpulan web dan membuat aplikasi web macet atau lambat merespons.

Satu hal yang saya perhatikan adalah bahwa asynchronous I / O bukanlah pilihan terbaik saat menangani operasi I / O yang sangat cepat. Dalam hal ini, manfaat dari tidak membuat thread sibuk sambil menunggu operasi I / O selesai tidak terlalu penting dan fakta bahwa operasi dimulai pada satu thread dan diselesaikan di thread lain menambah overhead pada keseluruhan eksekusi.

Anda dapat membaca penelitian lebih rinci yang baru-baru ini saya buat tentang topik asynchronous I / O vs. multithreading di sini .


Saya ingin tahu apakah ada gunanya membuat perbedaan antara operasi I / O yang diharapkan selesai, dan hal-hal yang mungkin tidak [misalnya, "dapatkan karakter berikutnya yang tiba di port serial", dalam kasus di mana perangkat jarak jauh mungkin atau mungkin tidak kirim apa saja]. Jika operasi I / O diharapkan selesai dalam waktu yang wajar, pembersihan sumber daya terkait dapat ditunda hingga operasi selesai. Namun, jika operasi mungkin tidak pernah selesai, penundaan tersebut tidak masuk akal.
supercat

@supercat skenario yang Anda gambarkan digunakan dalam aplikasi dan pustaka tingkat rendah. Server mengandalkannya, karena mereka terus menunggu koneksi masuk. Async I / O yang dijelaskan di atas tidak dapat masuk dalam skenario ini karena didasarkan pada memulai operasi tertentu dan mendaftarkan callback untuk penyelesaiannya. Jika Anda menjelaskan, Anda perlu mendaftarkan panggilan balik pada peristiwa sistem dan memproses setiap pemberitahuan. Anda terus memproses masukan daripada melakukan operasi. Seperti yang dikatakan, ini biasanya dilakukan pada level rendah, hampir tidak pernah di aplikasi Anda.
Florin Dumitrescu

Pola ini cukup umum pada aplikasi yang datang dengan berbagai jenis perangkat keras. Port serial tidak biasa seperti dulu, tetapi chip USB yang meniru port serial cukup populer dalam desain perangkat keras khusus. Karakter dari hal-hal seperti itu ditangani di tingkat aplikasi, karena OS tidak akan tahu bahwa urutan karakter masukan berarti, misalnya laci kas dibuka dan pemberitahuan harus dikirim ke suatu tempat.
supercat

Menurut saya, bagian tentang biaya CPU untuk memblokir IO tidak akurat: ketika dalam status pemblokiran, utas yang memicu IO pemblokiran akan ditunda oleh OS dan tidak membebani periode CPU hingga IO selesai sepenuhnya, hanya setelah itu apakah OS (diberitahu oleh interupsi) melanjutkan utas yang diblokir. Apa yang Anda jelaskan (menunggu sibuk dengan polling panjang) bukanlah bagaimana memblokir IO diimplementasikan di hampir semua runtime / compiler.
Lifu Huang

4

Alasan utama menggunakan AIO adalah untuk skalabilitas. Jika dilihat dalam konteks beberapa utas, manfaatnya tidak jelas. Namun saat sistem menskalakan hingga 1000 utas, AIO akan menawarkan kinerja yang jauh lebih baik. Peringatannya adalah bahwa pustaka AIO tidak boleh menyebabkan kemacetan lebih lanjut.


4

Untuk mengasumsikan peningkatan kecepatan karena segala bentuk multi-komputasi, Anda harus menganggap bahwa beberapa tugas berbasis CPU sedang dijalankan secara bersamaan pada beberapa sumber daya komputasi (umumnya inti prosesor) atau bahwa tidak semua tugas bergantung pada penggunaan bersamaan dari sumber daya yang sama - yaitu, beberapa tugas mungkin bergantung pada satu subkomponen sistem (misalnya penyimpanan disk) sementara beberapa tugas bergantung pada yang lain (menerima komunikasi dari perangkat periferal) dan yang lainnya mungkin memerlukan penggunaan inti prosesor.

Skenario pertama sering disebut sebagai pemrograman "paralel". Skenario kedua sering disebut sebagai pemrograman "konkuren" atau "asinkron", meskipun "konkuren" terkadang juga digunakan untuk merujuk pada kasus hanya mengizinkan sistem operasi untuk melakukan interleave eksekusi beberapa tugas, terlepas dari apakah eksekusi tersebut harus dilakukan tempatkan secara serial atau jika banyak sumber daya dapat digunakan untuk mencapai eksekusi paralel. Dalam kasus terakhir ini, "bersamaan" umumnya mengacu pada cara eksekusi ditulis dalam program, bukan dari perspektif simultanitas eksekusi tugas yang sebenarnya.

Sangat mudah untuk membicarakan semua ini dengan asumsi diam-diam. Misalnya, beberapa orang cepat membuat klaim seperti "Asynchronous I / O akan lebih cepat daripada I / O multi-threaded". Klaim ini meragukan karena beberapa alasan. Pertama, mungkin saja beberapa kerangka kerja I / O asinkron tertentu diimplementasikan secara tepat dengan multi-threading, dalam hal ini keduanya adalah satu konsep yang sama dan tidak masuk akal untuk mengatakan satu konsep "lebih cepat daripada" yang lain .

Kedua, bahkan dalam kasus ketika ada implementasi single-threaded dari framework asynchronous (seperti event loop single-threaded), Anda masih harus membuat asumsi tentang apa yang dilakukan loop tersebut. Misalnya, satu hal konyol yang dapat Anda lakukan dengan loop peristiwa single-threaded adalah memintanya untuk menyelesaikan dua tugas terikat CPU yang berbeda secara asinkron. Jika Anda melakukan ini pada mesin yang hanya memiliki inti prosesor tunggal yang ideal (mengabaikan pengoptimalan perangkat keras modern), maka melakukan tugas ini "secara asinkron" tidak akan benar-benar berfungsi dengan cara yang berbeda daripada menjalankannya dengan dua utas yang dikelola secara independen, atau hanya dengan satu proses - - perbedaannya mungkin terletak pada pengalihan konteks utas atau pengoptimalan jadwal sistem operasi, tetapi jika kedua tugas akan dikirim ke CPU, itu akan serupa dalam kedua kasus.

Berguna untuk membayangkan banyak kasus sudut yang tidak biasa atau bodoh yang mungkin Anda hadapi.

"Asynchronous" tidak harus serentak, misalnya seperti di atas: Anda "secara asinkron" menjalankan dua tugas yang terikat CPU pada mesin dengan tepat satu inti prosesor.

Eksekusi multi-utas tidak harus bersamaan: Anda menelurkan dua utas pada mesin dengan inti prosesor tunggal, atau meminta dua utas untuk memperoleh jenis sumber daya langka lainnya (bayangkan, katakanlah, basis data jaringan yang hanya dapat membuat satu koneksi pada satu waktu). Eksekusi utas mungkin disisipkan, namun penjadwal sistem operasi menganggapnya cocok, tetapi total runtime mereka tidak dapat dikurangi (dan akan ditingkatkan dari peralihan konteks utas) pada satu inti (atau lebih umum, jika Anda menelurkan lebih banyak utas daripada yang ada core untuk menjalankannya, atau memiliki lebih banyak utas yang meminta sumber daya daripada yang dapat dipertahankan sumber daya). Hal yang sama juga berlaku untuk multi-pemrosesan.

Jadi baik I / O asinkron maupun multi-threading tidak harus menawarkan peningkatan kinerja apa pun dalam hal waktu berjalan. Mereka bahkan bisa memperlambat segalanya.

Namun, jika Anda menentukan kasus penggunaan tertentu, seperti program tertentu yang membuat panggilan jaringan untuk mengambil data dari sumber daya yang terhubung ke jaringan seperti database jarak jauh dan juga melakukan beberapa komputasi terikat CPU lokal, Anda dapat mulai mempertimbangkannya. perbedaan kinerja antara kedua metode dengan asumsi tertentu tentang perangkat keras.

Pertanyaan untuk ditanyakan: Berapa banyak langkah komputasi yang perlu saya lakukan dan berapa banyak sistem sumber daya independen yang ada untuk melakukannya? Adakah subkumpulan dari langkah-langkah komputasi yang memerlukan penggunaan subkomponen sistem independen dan dapat mengambil manfaat dari melakukannya secara bersamaan? Berapa banyak inti prosesor yang saya miliki dan berapa biaya tambahan untuk menggunakan beberapa prosesor atau utas untuk menyelesaikan tugas pada inti terpisah?

Jika tugas Anda sangat bergantung pada subsistem independen, maka solusi asinkron mungkin bagus. Jika jumlah utas yang diperlukan untuk menanganinya akan banyak, sehingga peralihan konteks menjadi tidak sepele untuk sistem operasi, maka solusi asinkron beruntai tunggal mungkin lebih baik.

Setiap kali tugas terikat oleh sumber daya yang sama (misalnya beberapa kebutuhan untuk secara bersamaan mengakses jaringan atau sumber daya lokal yang sama), maka multi-threading mungkin akan menyebabkan overhead yang tidak memuaskan, dan sementara asinkron beruntai tunggal dapat menyebabkan lebih sedikit overhead, dalam sumber daya seperti itu- situasi terbatas itu juga tidak dapat menghasilkan percepatan. Dalam kasus seperti itu, satu-satunya pilihan (jika Anda ingin dipercepat) adalah membuat banyak salinan dari sumber daya tersebut tersedia (misalnya, beberapa inti prosesor jika sumber daya yang langka adalah CPU; database yang lebih baik yang mendukung lebih banyak koneksi bersamaan jika sumber daya langka adalah database dengan koneksi terbatas, dll.).

Cara lain untuk menjelaskannya adalah: mengizinkan sistem operasi untuk menyisipkan penggunaan sumber daya tunggal untuk dua tugas tidak bisa lebih cepat daripada hanya membiarkan satu tugas menggunakan sumber daya sementara yang lain menunggu, lalu membiarkan tugas kedua selesai secara berurutan. Selanjutnya, biaya penjadwal dari interleaving berarti dalam situasi nyata apa pun itu benar-benar menciptakan perlambatan. Tidak masalah jika penggunaan interleaved terjadi pada CPU, sumber daya jaringan, sumber daya memori, perangkat periferal, atau sumber daya sistem lainnya.


2

Salah satu kemungkinan implementasi I / O non-pemblokiran adalah persis seperti yang Anda katakan, dengan kumpulan thread latar belakang yang memblokir I / O dan memberi tahu thread pembuat I / O melalui beberapa mekanisme callback. Faktanya, beginilah cara kerja modul AIO di glibc. Berikut adalah beberapa detail samar tentang penerapannya.

Meskipun ini adalah solusi bagus yang cukup portabel (selama Anda memiliki utas), OS biasanya dapat melayani I / O non-pemblokiran dengan lebih efisien. Artikel Wikipedia ini mencantumkan kemungkinan penerapan selain kumpulan utas.


2

Saat ini saya sedang dalam proses mengimplementasikan async io pada platform tertanam menggunakan protothreads. Tanpa pemblokiran io membuat perbedaan antara berjalan pada 16000fps dan 160fps. Manfaat terbesar dari non blocking io adalah Anda dapat menyusun kode Anda untuk melakukan hal-hal lain sementara perangkat keras melakukan tugasnya. Bahkan inisialisasi perangkat dapat dilakukan secara paralel.

Martin


1

Di Node, beberapa utas diluncurkan, tetapi ini adalah lapisan bawah dalam waktu proses C ++.

"Jadi Ya, NodeJS adalah utas tunggal, tetapi ini setengah kebenaran, sebenarnya ini digerakkan oleh peristiwa dan utas tunggal dengan pekerja latar belakang. Perulangan peristiwa utama adalah utas tunggal tetapi sebagian besar pekerjaan I / O berjalan pada utas terpisah, karena I / O API di Node.js didesain secara asinkron / non-pemblokiran, untuk mengakomodasi loop peristiwa. "

https://codeburst.io/how-node-js-single-thread-mechanism-work-understanding-event-loop-in-nodejs-230f7440b0ea

"Node.js adalah non-pemblokiran yang berarti bahwa semua fungsi (callback) didelegasikan ke loop peristiwa dan mereka (atau dapat) dieksekusi oleh thread berbeda. Itu ditangani oleh run-time Node.js."

https://itnext.io/multi-threading-and-multi-process-in-node-js-ffa5bb5cde98 

Penjelasan "Node lebih cepat karena tidak memblokir ..." adalah sedikit pemasaran dan ini adalah pertanyaan yang bagus. Ini efisien dan dapat diskalakan, tetapi tidak sepenuhnya berulir tunggal.


0

Peningkatan sejauh yang saya tahu adalah bahwa Asynchronous I / O penggunaan (saya bicarakan MS System, hanya untuk memperjelas) sehingga disebut I / O port selesai . Dengan menggunakan panggilan Asynchronous, framework memanfaatkan arsitektur tersebut secara otomatis, dan ini diharapkan jauh lebih efisien daripada mekanisme threading standar. Sebagai pengalaman pribadi, saya dapat mengatakan bahwa Anda akan merasa aplikasi Anda lebih reaktif jika Anda lebih memilih AsyncCalls daripada memblokir utas.


0

Izinkan saya memberi Anda contoh balasan bahwa I / O asinkron tidak berfungsi. Saya menulis proxy yang mirip dengan di bawah ini menggunakan boost :: asio. https://github.com/ArashPartow/proxy/blob/master/tcpproxy_server.cpp

Namun, skenario kasus saya adalah, pesan masuk (dari sisi klien) cepat sementara yang keluar (ke sisi server) lambat untuk satu sesi, untuk mengimbangi kecepatan masuk atau untuk memaksimalkan throughput proxy total, kita harus menggunakan beberapa sesi dalam satu koneksi.

Jadi kerangka kerja I / O asinkron ini tidak berfungsi lagi. Kami membutuhkan kumpulan utas untuk dikirim ke server dengan menetapkan setiap utas sesi.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.