Halo semuanya!
Hari ini Anda akan menemukan teks panjang tanpa gambar (sedikit disingkat dibandingkan dengan aslinya), di mana tesis yang dimasukkan dalam judul dianalisis secara rinci. Veteran Microsoft,
Terry Crowley, menjelaskan esensi dari pemrograman asinkron dan menjelaskan mengapa pendekatan ini jauh lebih realistis dan lebih tepat daripada sinkron dan berurutan.
Mereka yang ingin atau berpikir untuk menulis buku yang menyentuh topik-topik seperti itu - menulis secara pribadi.
Sinkronisitas adalah mitos. Tidak ada yang terjadi secara instan. Semuanya butuh waktu.
Beberapa karakteristik sistem komputasi dan lingkungan pemrograman pada dasarnya didasarkan pada fakta bahwa perhitungan terjadi dalam dunia fisik tiga dimensi dan dibatasi oleh batas-batas kecepatan cahaya dan hukum-hukum termodinamika.
Akar seperti itu di dunia fisik berarti bahwa beberapa aspek tidak kehilangan relevansinya bahkan dengan munculnya teknologi baru yang memberikan peluang baru dan akses ke batas produktivitas baru. Mereka tetap valid karena mereka tidak hanya "pilihan yang dipilih selama desain", tetapi realitas yang mendasari Alam fisik.
Perbedaan antara sinkronisme dan asinkron dalam bahasa dan penciptaan sistem hanyalah aspek desain yang memiliki dasar fisik yang dalam. Sebagian besar programmer segera mulai bekerja dengan program dan bahasa tersebut, di mana eksekusi secara tersirat tersirat. Bahkan, ini sangat alami sehingga tidak ada yang bahkan menyebutkan atau membicarakannya secara langsung. Istilah "sinkron" dalam konteks ini berarti bahwa perhitungan dilakukan segera, seperti serangkaian langkah berturut-turut, dan tidak ada hal lain yang terjadi sebelum selesai. Saya menjalankan
“c = a + b” “x = f(y)”
- dan tidak ada lagi yang akan terjadi sampai instruksi ini selesai.
Tentu saja, tidak ada yang terjadi secara instan di alam semesta fisik. Semua proses dikaitkan dengan beberapa penundaan - Anda perlu menavigasi hierarki memori, menjalankan siklus prosesor, membaca informasi dari drive disk, atau menyambungkan ke node lain melalui jaringan, yang juga menyebabkan keterlambatan dalam pengiriman data. Semua ini adalah konsekuensi mendasar dari kecepatan cahaya dan perambatan sinyal dalam tiga dimensi.
Semua proses agak terlambat, semuanya butuh waktu. Mendefinisikan beberapa proses sebagai sinkron, kami, pada dasarnya, mengatakan bahwa kami akan mengabaikan penundaan ini dan menggambarkan perhitungan kami sebagai instan. Bahkan, dalam sistem komputer, infrastruktur serius sering diletakkan, yang memungkinkan Anda untuk terus aktif menggunakan perangkat keras dasar, bahkan ketika mereka mencoba untuk mengoptimalkan antarmuka pemrograman dengan menghadirkan peristiwa yang terjadi di dalamnya sebagai sinkron.
Gagasan bahwa sinkronisasi disediakan dengan menggunakan mekanisme khusus dan dikaitkan dengan biaya mungkin tampak tidak logis bagi programmer, yang lebih terbiasa dengan kenyataan bahwa asinkron yang memerlukan kontrol eksternal aktif. Sebenarnya, inilah yang sebenarnya terjadi ketika antarmuka asinkron disediakan: asinkroni fundamental asli terbuka untuk programmer sedikit lebih jelas daripada sebelumnya, dan ia harus memprosesnya secara manual, daripada mengandalkan program yang bisa melakukan ini secara otomatis. Pemberian asynchrony langsung dikaitkan dengan biaya tambahan untuk programmer, tetapi pada saat yang sama memungkinkan Anda untuk lebih kompeten mendistribusikan biaya dan trade-off yang melekat dalam bidang subjek ini, dan tidak meninggalkan ini pada belas kasihan sistem yang akan menyeimbangkan biaya dan trade-off tersebut. Antarmuka asinkron sering lebih akurat sesuai dengan peristiwa yang terjadi secara fisik di sistem dasar dan, karenanya, membuka kemungkinan optimasi tambahan.
Sebagai contoh, prosesor dan sistem memori dilengkapi dengan infrastruktur yang adil yang bertanggung jawab untuk membaca dan menulis data dalam memori, dengan mempertimbangkan hierarki. Pada level 1 (L1), tautan cache mungkin membutuhkan beberapa nanodetik, sedangkan tautan memori itu sendiri harus sepenuhnya melalui L2, L3 dan memori utama, yang mungkin membutuhkan ratusan nanodetik. Jika Anda hanya menunggu hingga tautan memori diselesaikan, prosesor akan menganggur untuk persentase waktu yang signifikan.
Mekanisme serius digunakan untuk mengoptimalkan fenomena seperti itu: pipelining dengan pandangan terkemuka dari aliran perintah, beberapa operasi simultan mengambil dari memori dan penyimpanan data saat ini, prediksi cabang dan upaya untuk lebih mengoptimalkan program, bahkan ketika itu melompat ke lokasi memori lain, kontrol akurat dari hambatan memori untuk menjamin bahwa semua mekanisme kompleks ini akan terus menyediakan model memori yang konsisten untuk lingkungan pemrograman tingkat yang lebih tinggi. Semua hal ini dilakukan dalam upaya untuk mengoptimalkan kinerja dan memanfaatkan perangkat keras secara maksimal untuk menyembunyikan keterlambatan 10-100 nanodetik ini dalam hierarki memori dan menyediakan sistem di mana eksekusi sinkron seharusnya terjadi, sambil tetap menekan kinerja yang layak dari inti prosesor.
Adalah jauh dari selalu jelas seberapa efektif optimasi tersebut untuk bagian kode tertentu, dan alat yang sangat spesifik untuk menganalisis kinerja sering diperlukan untuk menjawab pertanyaan ini. Pekerjaan analitis seperti itu disediakan untuk pengembangan beberapa kode yang sangat berharga (misalnya, seperti pada mesin konversi untuk Excel, beberapa opsi kompresi di kernel, atau jalur kriptografi dalam kode).
Operasi dengan penundaan yang lebih signifikan, misalnya, membaca data dari disk yang berputar, memerlukan penggunaan mekanisme lain. Dalam kasus seperti itu, ketika meminta untuk membaca dari disk OS, akan perlu untuk sepenuhnya beralih ke utas atau proses lain, dan permintaan sinkron akan tetap tidak terkirim. Biaya tinggi untuk beralih dan mendukung mekanisme ini dapat diterima, karena latensi tersembunyi dalam kasus ini dapat mencapai beberapa milidetik daripada nanodetik. Harap dicatat: biaya ini tidak dikurangi hanya dengan beralih di antara utas, tetapi termasuk biaya semua memori dan sumber daya, yang pada kenyataannya tidak digunakan sampai operasi selesai. Semua biaya ini harus digunakan untuk menyediakan antarmuka yang seharusnya sinkron.
Ada sejumlah alasan mendasar mengapa mungkin perlu untuk mengungkapkan asynchrony dasar nyata dalam sistem dan yang lebih disukai untuk menggunakan antarmuka asinkron dengan komponen, level atau aplikasi tertentu, bahkan dengan mempertimbangkan kebutuhan untuk secara langsung mengatasi meningkatnya kompleksitas.
Konkurensi Jika sumber daya yang disediakan dirancang untuk paralelisme sejati, antarmuka asinkron memungkinkan klien untuk secara lebih alami mengeluarkan beberapa permintaan dan mengelolanya, untuk memanfaatkan sepenuhnya sumber daya dasar.
Konveyorisasi . Cara biasa untuk mengurangi keterlambatan aktual pada beberapa antarmuka adalah untuk memastikan bahwa beberapa permintaan menunggu untuk dikirim pada waktu tertentu (seberapa banyak ini sebenarnya berguna dalam hal kinerja tergantung pada di mana kita mendapatkan sumber keterlambatan). Dalam kasus apa pun, jika sistem diadaptasi untuk pipelining, maka penundaan aktual dapat dikurangi dengan faktor yang sama dengan jumlah permintaan yang menunggu untuk dikirim. Jadi, mungkin diperlukan 10 ms untuk menyelesaikan permintaan tertentu, tetapi jika Anda menulis 10 permintaan ke pipa, responsnya bisa datang setiap milidetik. Total throughput adalah fungsi dari pipelining yang tersedia, dan bukan hanya penundaan “pass-through” per permintaan. Antarmuka yang sinkron yang mengeluarkan permintaan dan menunggu respons akan selalu memberikan penundaan ujung ke ujung yang lebih tinggi.
Pengepakan (lokal atau jarak jauh) . Antarmuka asinkron secara lebih alami menyediakan implementasi sistem pengemasan permintaan, baik secara lokal maupun pada sumber daya jarak jauh (catatan: dalam hal ini, "disk" di ujung lain dari antarmuka I / O dapat menjadi "jarak jauh"). Faktanya adalah bahwa aplikasi harus sudah mengatasi menerima tanggapan, dan pada saat yang sama akan ada beberapa penundaan, karena aplikasi tidak akan mengganggu pemrosesan saat ini. Pemrosesan tambahan seperti itu dapat digabungkan dengan permintaan tambahan yang secara alami akan dibundel.
Kumpulan lokal dapat memberikan transfer serangkaian permintaan yang lebih efisien, atau bahkan kompresi dan penghapusan permintaan duplikat langsung pada mesin lokal. Agar dapat secara bersamaan mengakses seluruh rangkaian permintaan pada sumber daya jarak jauh, optimasi yang serius mungkin diperlukan. Contoh klasik: pengontrol disk mengatur ulang serangkaian operasi baca dan tulis untuk memanfaatkan posisi head disk pada pelat yang berputar dan untuk meminimalkan waktu pengumpanan head. Pada antarmuka penyimpanan data apa pun yang beroperasi pada level blok, Anda dapat secara serius meningkatkan kinerja dengan menggabungkan serangkaian kueri di mana semua operasi baca dan tulis jatuh pada blok yang sama.
Secara alami, pengemasan lokal juga dapat diimplementasikan pada antarmuka yang sinkron, tetapi untuk ini Anda harus “menyembunyikan kebenaran” sebagian besar atau paket paket program sebagai fitur khusus antarmuka, yang secara signifikan dapat menyulitkan seluruh klien. Contoh klasik menyembunyikan kebenaran adalah buffer I / O. Aplikasi ini memanggil
“write(byte)”
, dan antarmuka mengembalikan
success
, tetapi, pada kenyataannya, catatan itu sendiri (serta informasi apakah itu berhasil diloloskan) tidak akan terjadi sampai buffer secara eksplisit diisi atau kosong, dan ini terjadi ketika file ditutup . Banyak aplikasi dapat mengabaikan detail seperti itu - kekacauan hanya terjadi ketika aplikasi perlu menjamin beberapa urutan operasi yang saling berinteraksi, serta gagasan sebenarnya tentang apa yang terjadi di tingkat bawah.
Buka kunci / Lepaskan . Salah satu penggunaan asynchrony yang paling umum dalam konteks antarmuka pengguna grafis adalah untuk mencegah agar utas utama antarmuka pengguna tidak diblokir sehingga pengguna dapat terus berinteraksi dengan aplikasi. Penundaan dalam operasi jangka panjang (seperti komunikasi jaringan) tidak dapat disembunyikan di balik antarmuka sinkron. Dalam hal ini, utas antarmuka pengguna harus secara eksplisit mengelola operasi seperti sinkronisasi dan mengatasi kompleksitas tambahan yang dimasukkan ke dalam program.
Antarmuka pengguna hanyalah contoh di mana komponen harus terus menanggapi permintaan tambahan dan, oleh karena itu, tidak dapat mengandalkan beberapa mekanisme standar yang menyembunyikan penundaan untuk menyederhanakan pekerjaan programmer.
Komponen server web yang menerima koneksi baru ke soket, sebagai suatu peraturan, akan dengan cepat mentransfer koneksi seperti itu ke komponen asinkron lain yang menyediakan komunikasi pada soket, dan dengan sendirinya akan kembali memproses permintaan baru.
Dalam model sinkron, komponen dan model pemrosesan mereka biasanya terkait erat.
Interaksi asinkron adalah mekanisme yang
sering digunakan untuk melonggarkan ikatan .
Pengurangan biaya dan manajemen. Seperti disebutkan di atas, mekanisme apa pun untuk menyembunyikan sinkronisasi tidak melibatkan alokasi sumber daya dan overhead. Untuk aplikasi tertentu, overhead seperti itu mungkin tidak dapat diterima, dan perancang aplikasi itu harus menemukan cara untuk mengontrol asinkroni alami.
Contoh yang menarik adalah sejarah server web. Server web awal (dibangun di atas Unix) biasanya menggunakan proses terpisah untuk mengelola permintaan yang masuk. Kemudian proses ini dapat membaca hubungan ini dan menulis kepadanya, itu terjadi, pada dasarnya, secara serempak. Desain seperti itu dikembangkan, dan biaya berkurang ketika utas mulai digunakan alih-alih proses, tetapi keseluruhan model eksekusi sinkron dipertahankan. Dalam opsi desain modern, diakui bahwa perhatian utama tidak boleh diberikan pada model perhitungan, tetapi, pertama-tama, untuk input / output terkait yang berkaitan dengan membaca dan menulis ketika bertukar informasi dengan database, sistem file atau mengirimkan informasi melalui jaringan, sambil merumuskan respons . Biasanya, antrian kerja digunakan untuk ini, di mana batas tertentu pada jumlah utas diperbolehkan - dan dalam hal ini, dimungkinkan untuk lebih jelas membangun manajemen sumber daya.
Keberhasilan NodeJS dalam pengembangan backend dijelaskan tidak hanya dengan dukungan mesin ini dari sisi banyak pengembang JavaScript yang tumbuh menciptakan antarmuka web klien. Dalam NodeJS, seperti dalam skrip browser, perhatian besar diberikan untuk mendesain secara asinkron, yang berjalan dengan baik dengan opsi beban server yang khas: mengelola sumber daya server tergantung terutama pada I / O, dan bukan pada pemrosesan.
Ada aspek menarik lainnya: pertukaran semacam itu lebih eksplisit dan lebih baik disesuaikan oleh pengembang aplikasi, jika Anda mematuhi pendekatan asinkron. Dalam contoh dengan keterlambatan dalam hirarki memori, penundaan aktual (diukur dalam siklus prosesor dalam hal permintaan dalam memori) meningkat secara dramatis selama beberapa dekade. Pengembang prosesor sedang berjuang untuk menambah level cache yang baru dan mekanisme tambahan yang semakin mendorong model memori yang disediakan oleh prosesor sehingga tampilan pemrosesan yang sinkron dapat dipertahankan lebih lanjut.
Pergantian konteks di perbatasan I / O sinkron adalah contoh lain di mana trade-off aktual telah berubah secara dramatis dari waktu ke waktu. Peningkatan dalam siklus prosesor lebih cepat daripada melawan penundaan, dan ini berarti bahwa sekarang aplikasi kehilangan lebih banyak kemampuan komputasi, sementara itu menganggur dalam bentuk terkunci, menunggu penyelesaian IO. Masalah yang sama terkait dengan biaya relatif kompromi telah menyebabkan perancang OS untuk tetap berpegang pada skema manajemen memori yang jauh lebih mirip dengan model sebelumnya dengan proses swapping (di mana seluruh gambar proses dimuat penuh ke dalam memori, setelah proses dimulai), alih-alih bertukar halaman. Terlalu sulit untuk menyembunyikan penundaan yang mungkin terjadi di perbatasan setiap halaman. Total throughput yang ditingkatkan secara dramatis dicapai dengan permintaan IO berurutan yang besar (dibandingkan dengan menggunakan permintaan acak) juga berkontribusi pada perubahan seperti itu saja.
Topik lainnyaBatalkanPembatalan adalah topik yang kompleks . Secara historis, sistem yang berorientasi secara sinkron melakukan pekerjaan yang buruk dengan pemrosesan pembatalan, dan beberapa bahkan tidak mendukung pembatalan sama sekali. Pembatalan pada dasarnya harus dirancang "out of band", untuk operasi seperti itu diperlukan untuk memanggil utas eksekusi yang terpisah. Sebagai alternatif, model asinkron cocok, di mana dukungan untuk pembatalan diatur secara lebih alami, khususnya, pendekatan sepele seperti itu digunakan: ia mengabaikan respons mana yang akhirnya kembali (dan apakah ia kembali sama sekali). Pembatalan menjadi semakin penting ketika variabilitas keterlambatan meningkat, dan dalam praktiknya, tingkat kesalahan juga meningkat - yang memberikan irisan sejarah yang sangat baik yang menunjukkan bagaimana lingkungan jaringan kami berkembang.
Pembatasan / Manajemen Sumber DayaDesain sinkron, menurut definisi, memaksakan beberapa pembatasan, mencegah aplikasi dari mengeluarkan permintaan tambahan sampai permintaan saat ini selesai. Dalam desain asinkron, pelambatan tidak terjadi secara sia-sia, sehingga terkadang perlu untuk mengimplementasikannya secara eksplisit.
Posting ini menjelaskan situasi dengan Word Web App sebagai contoh, di mana transisi dari desain sinkron ke asinkron menyebabkan masalah serius dengan manajemen sumber daya. Jika aplikasi menggunakan antarmuka sinkron, maka mungkin tidak mengenali bahwa pelambatan tertanam secara implisit dalam kode. Ketika menghapus pelambatan tersirat seperti itu, adalah mungkin (atau perlu) untuk mengatur manajemen sumber daya secara lebih eksplisit.
Saya harus berurusan dengan ini di awal karir saya ketika kami mengirim editor teks dari API grafis sinkron Sun ke X Windows. Saat menggunakan Sun API, operasi rendering sinkron, sehingga klien tidak mendapatkan kontrol kembali sampai selesai. Di X Windows, permintaan grafis dikirim secara tidak sinkron melalui koneksi jaringan, dan kemudian dieksekusi oleh server tampilan (yang bisa pada mesin yang sama atau berbeda).
Untuk memastikan kinerja interaktif yang baik, aplikasi kita harus menyediakan render (yaitu, memastikan bahwa baris di mana kursor sekarang diperbarui dan dirender), dan kemudian memeriksa apakah ada input keyboard lain yang perlu dibaca. , ( , ), , . API. , , - . , . UI , .
, 30 (-, Facebook iPhone ). – ( , ), , . , , .
, . , Microsoft, , API – , , . , , – : «, !» , , .
, . – , . , : , , , . , - . , ,
async/await
. «» , , , JavaScript. : , .
Async/await
, , . . , , , .
. , , . , , , . , , ( !). () , , .
, . , . async/await, , , , .
, , , – . , . – , , , ( , – Word Excel). , , - , , , .
, , , , .
, – . .
Kesimpulan. – , , . , , , . , ; , .