Asinkroni bukanlah konkurensi
(kristoff.it)- Asinkroni dan konkurensi adalah konsep yang sering tertukar, tetapi maknanya berbeda
- Asinkroni berarti adanya kemungkinan bahwa tugas-tugas dapat dijalankan tanpa bergantung pada urutan
- Konkurensi berarti kemampuan sistem untuk menjalankan beberapa tugas secara bersamaan
- Tidak adanya pemisahan yang jelas antara dua konsep ini dalam ekosistem bahasa dan library menimbulkan inefisiensi dan kompleksitas
- Dalam bahasa Zig, pemisahan antara asinkroni dan konkurensi memungkinkan kode sinkron dan asinkron hidup berdampingan tanpa duplikasi kode
Pendahuluan: perlunya membedakan asinkroni dan konkurensi
Lewat presentasi terkenal dari Rob Pike, kalimat "konkurensi bukanlah paralelisme" sudah dikenal luas, tetapi ada poin yang secara praktis lebih penting dari itu. Yaitu perlunya konsep "asinkroni". Menurut definisi Wikipedia,
- Konkurensi: kemampuan sistem untuk menangani beberapa tugas pada saat yang sama melalui pembagian waktu atau secara paralel
- Komputasi paralel: menjalankan beberapa tugas secara serentak pada level fisik yang nyata
Di luar itu, ada satu konsep penting yang sering kita lewatkan, yaitu "asinkroni".
Contoh 1: menyimpan dua file
Saat menyimpan dua file (A, B) dan urutannya tidak penting,
io.async(saveFileA, .{io})
io.async(saveFileB, .{io})
- Menyimpan A lebih dulu atau B lebih dulu sama-sama tidak masalah, dan menyimpannya secara bergantian di tengah proses juga tidak menimbulkan masalah
- Bahkan jika file A disimpan seluruhnya lalu file B baru dimulai, secara kode itu tetap benar
Contoh 2: dua socket (server, klien)
Saat harus membuat server TCP dan menghubungkan klien di dalam program yang sama,
io.async(Server.accept, .{server, io})
io.async(Client.connect, .{client, io})
- Dalam kasus ini, eksekusi dua tugas tersebut harus benar-benar berjalan saling tumpang tindih
- Artinya, saat server menerima koneksi, klien juga harus mencoba melakukan koneksi
- Jika diproses secara serial seperti contoh file pertama, perilaku yang diinginkan tidak akan muncul
Merapikan konsep
Konsep asinkroni, konkurensi, dan paralelisme didefinisikan sebagai berikut
- Asinkroni (asynchrony) : sifat bahwa tugas-tugas tetap menghasilkan hasil yang benar walaupun dieksekusi di luar urutan
- Konkurensi (concurrency) : kemampuan untuk menjalankan beberapa tugas secara bersamaan, baik secara paralel maupun melalui eksekusi terbagi
- Paralelisme (parallelism) : kemampuan untuk secara fisik menjalankan beberapa tugas secara serentak dalam waktu nyata
Dua contoh, penyimpanan file dan koneksi socket, sama-sama asinkron, tetapi yang kedua (server-klien) benar-benar membutuhkan konkurensi
Manfaat praktis membedakan asinkroni dan konkurensi
Jika pembedaan ini tidak dilakukan, masalah berikut akan muncul
- Pembuat library harus menulis dua versi kode, untuk asinkron dan sinkron (misalnya: redis-py vs asyncio-redis)
- Pengguna akan menghadapi sifat "menular" dari kode asinkron, sehingga hanya karena satu dependensi library asinkron saja, seluruh proyek harus diubah menjadi asinkron, yang menimbulkan ketidaknyamanan
- Untuk menghindarinya, sering muncul jalan pintas yang tidak ideal, dan ini kerap memicu deadlock serta inefisiensi
Karena itu, pemisahan yang jelas antara dua konsep ini memberi keuntungan besar bagi pembuat library maupun pengguna
Zig: memisahkan asinkroni dan konkurensi
Bahasa Zig menggunakan io.async untuk asinkroni, tetapi ini tidak menjamin konkurensi
- Artinya, meskipun memakai
io.async, secara internal ia tetap bisa berjalan dalam mode single-thread dan blocking - Sebagai contoh
kode ini dalam lingkungan blocking dapat berperilaku sama denganio.async(saveFileA, .{io}) io.async(saveFileB, .{io})saveFileA(io) saveFileB(io) - Dengan kata lain, meskipun pembuat library menggunakan
io.async, pengguna tetap punya fleksibilitas untuk menjalankannya sebagai I/O blocking berurutan jika diinginkan
Pengenalan konkurensi dan mekanisme pergantian tugas (scheduling)
Jika konkurensi dibutuhkan, agar benar-benar bekerja efektif maka diperlukan
- I/O berbasis event yang non-blocking (epoll, io_uring, dan sebagainya)
- Primitive pergantian tugas (switching) seperti
yield
- Sebagai contoh, Zig menggunakan teknik stack swapping di lingkungan green thread untuk melakukan pergantian tugas
- Mirip dengan penjadwalan thread di level OS, status seperti register CPU dan stack disimpan/dipulihkan untuk berpindah di antara beberapa task
- Mekanisme pergantian semacam ini diperlukan agar kode asinkron benar-benar dapat dijadwalkan secara konkuren
- Implementasi coroutine tanpa stack (misalnya
suspend,resume) juga mengikuti prinsip yang sama
Koeksistensi kode sinkron dan kode asinkron
Jika dua pemanggilan saveData dijalankan dengan io.async seperti di bawah,
io.async(saveData, .{io, "a", "b"})
io.async(saveData, .{io, "c", "d"})
- Karena kedua tugas tersebut bersifat asinkron satu sama lain, bahkan jika fungsinya ditulis secara sinkron di dalam, tugas-tugas itu tetap bisa secara alami dijadwalkan dalam konteks konkurensi
- Pengguna maupun pembuat library dapat memakai fungsi sinkron/asinkron bersama-sama tanpa masalah dan tanpa duplikasi kode
Menyatakan situasi saat konkurensi itu "wajib"
Fungsi tertentu (misalnya accept pada server TCP) perlu mengekspresikan di dalam kode bahwa saat dijalankan, fungsi itu secara eksplisit membutuhkan konkurensi
- Di Zig, ini dibedakan melalui fungsi eksplisit seperti
io.asyncConcurrent - Pendekatan seperti ini akan memunculkan error jika lingkungan eksekusi tidak mendukung konkurensi untuk tugas tersebut
- Berbeda dengan
io.asyncyang bertujuan untuk asinkroni, di sini jaminan konkurensi wajib ada, sehingga diimplementasikan sebagai fungsi yang bisa gagal
Kesimpulan
- Asinkroni dan konkurensi adalah konsep yang sepenuhnya berbeda dan harus dibedakan dengan jelas
- Kode sinkron dan kode asinkron dapat hidup berdampingan
- Model asinkroni/konkurensi Zig memungkinkan kedua dunia itu dimanfaatkan bersama tanpa duplikasi kode
- Struktur seperti ini juga telah diterapkan pada bahasa lain seperti Go, dan menawarkan jalan untuk mengatasi sifat menular dari async/await
- Melalui desain async I/O baru Zig, ke depan kita bisa mengharapkan lingkungan pemrograman konkurensi/asinkroni yang lebih intuitif
1 komentar
Opini Hacker News
definisi async memang terasa sangat sulit, saya juga salah satu dari beberapa orang yang merancang async di JavaScript, dan saya tidak setuju dengan definisi yang diajukan tulisan ini, sesuatu tidak otomatis bekerja benar hanya karena bersifat async, bahkan di kode async pun berbagai jenis race condition level pengguna tetap bisa muncul, baik bahasanya mendukung async/await maupun tidak, definisi yang belakangan saya pakai adalah bahwa async adalah “kode yang secara eksplisit distrukturkan untuk concurrency”, meski sudut pandang ini pun masih perlu dirapikan lebih lanjut, saya juga punya tulisan sendiri soal ini, lihat Quite a few words about async
saya rasa penting untuk membedakan konsep abstrak asynchronism dengan implementasi nyatanya, yang terakhir ini mencakup baik abstraksi di tingkat bahasa maupun sarana koordinasi mekanis, pada tingkat abstraksi tertinggi, lawan dari synchronism memanglah asynchronism, biasanya saat beberapa pihak harus bekerja bersama (misalnya satu pekerjaan harus selesai dulu sebelum pekerjaan lain bisa lanjut), inti dari asynchronism adalah ketika kita tidak tahu atau tidak mendefinisikan kapan hal itu terjadi, definisi ini sendiri tidak sulit, masalahnya muncul pada beban kognitif saat merancang abstraksi seperti ini di tingkat bahasa
saya memang tidak terlalu mendalami topik ini, tapi menurut saya kode async itu pada dasarnya mengubah pekerjaan yang semula blocking menjadi non-blocking agar pekerjaan lain bisa berjalan bersamaan, khususnya dalam kasus saya, kode yang blocking terlalu lama di embedded loop bisa merusak I/O dan memicu gangguan yang terlihat atau terdengar, jadi sudut pandang ini terasa sangat jelas
saya bahkan ragu apakah async memang perlu didefinisikan, alasan kenapa susah didefinisikan mungkin karena tidak ada satu konsep pun yang benar-benar pas untuk semuanya, saya juga ragu apakah async atau event loop memang perlu didefinisikan secara ketat, di area chip fisik tempat pemrosesan paralel sungguhan bisa terjadi, pasti ada banyak konsep yang saya sendiri tidak tahu, bagi saya cukup tahu “user finger” (misalnya sentuhan jari) dan “quickies” (pekerjaan yang waktu jalannya sangat singkat), job queue, serta API blocking/non-blocking, untuk mencapai tujuan saya, API non-blocking lebih baik, karena pekerjaan yang memakan waktu lama saya serahkan ke subsistem bawah, dan saya hanya menulis “quicky” seperti menyimpan data yang saya mau, lalu mendefinisikan quicky berbeda untuk sukses/gagal, pembedaan sync dan async sendiri tidak terlalu membantu, tentu saya tetap perlu memahami konsepnya saat orang lain membicarakannya, pada dasarnya async bagi saya adalah API non-blocking, model pemrograman async pada praktiknya adalah menulis pekerjaan blocking yang kecil dan atomik (dilihat dari waktu eksekusi) agar selaras dengan event yang “chaotic dan non-deterministic”, apa pun yang dikerjakan sistem di dalam, saya percaya browser, OS, atau perangkat itu sendiri menyediakan multi execution unit dan scheduler yang baik, async bagi saya konsep yang didefinisikan secara kabur, dan bahkan kalau bisa didefinisikan pun saya ragu itu akan benar-benar berguna, justru konsep seperti event, sifat blocking dari pekerjaan yang saya tulis, function closure, dan apa saja yang saat memakai API dipecah menjadi job lain terasa jauh lebih praktis, istilah “callback” sendiri saat awal belajar sangat membingungkan, saya kira kode berhenti di sana, padahal sebenarnya bagian itu tetap dieksekusi sampai selesai lalu saya harus memahami secara presisi kode apa yang akan berjalan saat “callback” dipanggil, dan informasi apa yang bisa dilihatnya, terus terang ini sekaligus chaos dan ide yang jenius, jauh lebih sederhana memahami model dasarnya yaitu event, pekerjaan blocking, job queue, dan API non-blocking, serta penting juga memahami apa yang saya kerjakan dan apa yang dilakukan browser/OS dan lainnya, misalnya di cpp kita mendeklarasikan model concurrent sementara OS yang benar-benar mengeksekusinya, di JS kita mendeklarasikan ke browser atau Node bahwa “mungkin” ada concurrency lewat API non-blocking, lalu mereka menanganinya secara concurrent di internal, yang paling penting adalah menjaga tiap pekerjaan tetap singkat (<50ms) dan cukup menyatakan niat lewat API non-blocking, cpp atau rust memberi tahu OS untuk menjalankan task secara concurrent sehingga bahkan jika secara fisik hanya ada satu thread, responsivitas UI tetap terjaga, pada akhirnya yang perlu dilakukan programmer async adalah membuat “model UX yang keren” dan memetakan event dengan baik ke quickies
sepertinya penulis memisahkan “konsep yield” dari definisi concurrency lalu memasukkannya ke istilah baru “asynchrony”, dan mengklaim bahwa tanpa konsep ini seluruh concurrency runtuh, menurut saya yield sejak awal memang esensial bagi concurrency sehingga sudah merupakan konsep yang melekat di dalamnya, ini memang konsep penting, tetapi memisahkannya menjadi istilah baru hanya menambah kebingungan
menurut saya paralelisme 1:1 adalah salah satu bentuk concurrency tanpa yield, selain itu semua concurrency non-paralel harus menyerahkan eksekusi (yield) dalam siklus tertentu, bahkan di level instruksi pun demikian, misalnya di CUDA, thread yang bercabang dalam warp yang sama akan saling mengeksekusi instruksi secara bergantian sehingga satu cabang bisa mem-block cabang lainnya
saya justru ingin menekankan bahwa tulisan yang dikutip secara eksplisit menyatakan “yield adalah konsep concurrency”
concurrency tidak selalu berarti yield, logika sinkron membutuhkan sinkronisasi yang jelas, dan yield hanyalah salah satu sarana sinkronisasi, yang saya maksud dengan logika async adalah concurrency yang berjalan tanpa sinkronisasi maupun yield, dari sudut pandang praktik, concurrency maupun logika async tidak pernah sepenuhnya ada pada mesin von Neumann
dalam konteks ini, async adalah abstraksi yang memisahkan persiapan/pengajuan request dari pengambilan hasilnya, ini memungkinkan kita mengajukan beberapa request dulu baru kemudian memeriksa hasilnya, implementasi concurrent dimungkinkan tetapi tidak wajib, meski begitu tujuan abstraksi ini memang untuk mendapatkan concurrency, tanpa concurrency tidak ada manfaat yang ingin diperoleh, beberapa abstraksi async bahkan tidak mungkin diimplementasikan tanpa tingkat concurrency minimum, misalnya model callback masih bisa ditiru di single thread tetapi punya batasan seperti deadlock saat memegang mutex non-rekursif, artinya abstraksi async tanpa concurrency pasti akan gagal, pihak peminta bisa saja mengajukan request sambil memegang mutex, lalu jika callback dieksekusi sebelum unlock, unlock itu bisa tidak pernah terjadi, minimal harus ada thread terpisah agar peminta bisa mencapai unlock
“cooperative multitasking bukan preemptive”, istilah “async” biasanya berarti “single thread, cooperative multitasking (yield eksplisit) dan berbasis event”, dengan operasi eksternal berjalan secara concurrent dan melaporkan hasil lewat event, dalam model eksekusi multithread atau concurrent, async tidak terlalu bermakna, walau thread itu terblokir program tetap berjalan, jadi yield point tidak perlu eksplisit
ide IO baru Zig terlihat segar untuk pengembangan aplikasi umum, dan optimal bagi orang yang tidak membutuhkan stackless coroutine, tetapi untuk penulisan library saya rasa ini akan lebih rawan kesalahan, penulis library sulit mengetahui apakah IO yang diberikan itu single-thread atau multi-thread, event-based IO atau bukan, kode terkait concurrency/async/parallel saja sulit ditulis bahkan saat kita sepenuhnya memahami IO stack, jadi jika IO diberikan dari luar, kesulitannya berlipat, jika interface IO menjadi sebesar “OS kecil”, jumlah skenario yang harus diuji juga akan meledak, saya belum yakin apakah hanya dengan primitive async yang disediakan interface, semua edge case nyata bisa ditangani, untuk mendukung berbagai implementasi IO, kodenya harus sangat “defensif” dan seolah selalu mengasumsikan IO yang paling paralel, khususnya saya menduga tidak akan mudah mencampur stackless coroutine dengan pendekatan ini, untuk mengurangi spawn coroutine yang tidak perlu, kita akan butuh polling coroutine secara eksplisit, dan saya rasa kebanyakan developer tidak akan menulis kode seperti itu sendiri, pada akhirnya mungkin akan bermuara ke struktur yang mirip kode async/await biasa, jika mempertimbangkan dynamic dispatch dan kecenderungan desain bottom-up Zig, pada akhirnya ini akan menjadi bahasa yang cukup high-level, belum ada kasus penerapan nyata, jadi menyebutnya pendekatan “tanpa kompromi” terasa terlalu berani, penilaian yang sesungguhnya baru mungkin setelah dipakai selama beberapa tahun
stackless coroutine memang tetap akan didukung, karena itu diperlukan untuk dukungan target WASM jadi pasti akan masuk, dynamic dispatch hanya dipakai saat ada 2 implementasi IO atau lebih, kalau cuma satu maka akan diganti menjadi direct call, karena belum tervalidasi di lapangan, saya juga menganggap istilah “tanpa kompromi” masih terlalu dini, saya dengar bahasa Jai berhasil memakai model serupa (bedanya memakai konteks IO implisit, bukan context passing eksplisit), tetapi itu pun sulit disebut benar-benar terpakai di dunia nyata
saya setuju bahwa untuk mendukung eksekusi sinkron dan asinkron sekaligus, kode harus selalu mengasumsikan IO yang paling paralel, tetapi jika async diimplementasikan dengan baik di level handler event IO yang lebih bawah, kita cukup menerapkan prinsip yang sama di semua tempat, bahkan dalam kasus terburuk, kodenya hanya akan berjalan secara berurutan (dan lambat), bukan jatuh ke masalah race/deadlock
saya suka sekali ide Zig karena kita tidak perlu memakai dua library terpisah, tetapi pengujian kode async selalu membuat saya khawatir, saya tidak tahu bagaimana kita bisa yakin bahwa test yang lulus hari ini benar-benar mereplikasi semua skenario/urutan yang akan terjadi di lapangan, program thread juga punya masalah yang sama, tetapi kode multithread selalu lebih sulit ditulis dan di-debug, saya sendiri sebisa mungkin menghindari thread, masalah sebenarnya adalah ‘membuat developer benar-benar memahami lingkungan async/thread’, baru-baru ini saya bekerja dengan tim yang di sistem Python-nya memakai setengah JS dan setengah Python, lalu mengubah basis kode besar menjadi async dan threaded, tetapi mereka bahkan tidak tahu apa itu Global Interpreter Lock (GIL), ucapan saya mungkin terdengar cuma seperti omelan, lebih parah lagi, test mereka selalu lolos bahkan saat kodenya dirusak, mangum memaksa background dan pekerjaan async untuk finishing saat HTTP request berakhir, tetapi mereka tidak tahu itu, dan bahkan ketika hal-hal seperti ini dijelaskan, orang-orang tetap tidak terlalu peduli, yang penting bukan hanya mengetahui, tetapi apakah orang lain benar-benar mau memperhatikannya
di Zig ada rencana menghadirkan implementasi pengujian untuk Io, sehingga kita bisa melakukan stress test seperti fuzz testing di bawah model eksekusi paralel, tetapi intinya sebagian besar kode library kemungkinan tidak akan perlu memanggil io.async atau io.asyncConcurrent secara langsung, misalnya kebanyakan library database cukup ditulis dengan kode sinkron murni, lalu developer aplikasi bisa mengubahnya menjadi async dengan mudah seperti io.async(writeToDb), io.async(doOtherThing), pendekatan seperti ini lebih sedikit rawan kesalahan dan jauh lebih mudah dipahami dibanding menaburkan async/await ke seluruh kode
sangat setuju, menguji semua interleaving dalam kode async dan multithread terkenal sangat sulit, bahkan dengan fuzzer atau framework pengujian concurrency pun sulit merasa yakin tanpa pelajaran dari produksi nyata, dalam distributed system ini lebih buruk lagi, misalnya saat merancang infrastruktur webhook, kita bukan cuma berurusan dengan async di kode kita sendiri, tetapi juga dengan retry jaringan, timeout, partial failure, dan berbagai masalah eksternal lain, dalam lingkungan high-concurrency, retry, deduplication, dan jaminan idempotency menjadi isu engineering tersendiri, karena itu muncul kebutuhan akan layanan khusus seperti Vartiq.com (saya bekerja di sana), layanan seperti ini bisa mengabstraksikan sebagian kompleksitas concurrency operasional dan mengurangi blast radius, tetapi masalah pengujian async di kode saya sendiri tetap ada, pada akhirnya async, threading, dan distributed concurrency saling memperbesar risiko, jadi komunikasi dan desain sistem jauh lebih penting daripada sintaks atau library apa pun
saya rasa penulis bingung dalam mendefinisikan concurrency, layak merujuk ke makalah Lamport
tolong jangan cuma meninggalkan tautan makalah tanpa penjelasan, menurut saya definisinya sendiri cukup masuk akal, misalnya async: jika pekerjaan tetap benar walau tidak dieksekusi berurutan maka itu async, concurrency: sifat sistem yang memungkinkan banyak pekerjaan berjalan bersamaan, entah lewat paralelisme atau task switching, parallelism: dua atau lebih pekerjaan benar-benar berjalan pada saat yang sama di level fisik
karena alasan inilah saya berhenti total memakai istilah-istilah itu, dengan siapa pun saya bicara pemahamannya selalu berbeda, sehingga istilah itu sendiri tidak lagi bermakna untuk komunikasi
penulis juga tahu bahwa istilah itu sudah punya definisi yang ada sebelumnya di tulisan blognya, dia sekadar mengusulkan definisi baru, dan selama definisi itu konsisten, itu sudah cukup, perbedaannya hanya apakah pembaca mau menerimanya atau tidak
separuh dari makalah Lamport secara konseptual tidak bisa diekspresikan di kebanyakan bahasa, membuat thread bukan berarti kita akan membahas total order dan partial order, pembahasan seperti itu biasanya baru diperlukan saat merancang protokol dengan TLA+, tidak perlu menyebutnya sebagai teori baru hanya karena di API async Zig ada fungsi yang memunculkan compile error bila “hanya bekerja di lingkungan eksekusi asinkron”
cara yang baik untuk menilai apakah istilah “asynchrony” benar-benar diperlukan adalah melihat apakah ia berguna bukan hanya di satu bahasa/model, tetapi juga di berbagai model concurrency lain, misalnya Haskell, Erlang, OCaml, Scheme, Rust, Go, dan sebagainya, jika istilah itu dibutuhkan secara umum di berbagai lingkungan, nilainya tinggi, secara umum saat cooperative scheduling dipakai, seluruh sistem bisa lockup atau mengalami latency hanya karena masalah di satu bagian kode sehingga perlu perhatian ekstra, sedangkan pada preemptive scheduling banyak masalah seperti ini hilang, karena lockup seluruh sistem menjadi tidak mungkin, kelompok masalahnya berkurang besar
“asynchrony” adalah kata yang kurang tepat dalam kasus ini, sudah ada istilah matematis yang terdefinisi baik yaitu “commutativity”, beberapa operasi tidak peduli urutan (penjumlahan, perkalian, dan lain-lain), sebagian lain peduli urutan (pengurangan, pembagian, dan lain-lain), biasanya urutan operasi dalam kode direpresentasikan oleh nomor baris (atas ke bawah), sedangkan pada kode async urutan ini rusak, karena itu asyncConcurrent(...) yang ditulis seperti ini memang tak bisa tidak terasa membingungkan, kalau belum benar-benar memahami isi tulisan blognya, sulit menangkap maksudnya, di Zig (dan Rust yang juga saya suka) pendekatan “hipster” seperti ini cukup sering muncul, lebih baik menerapkan skema commutativity/ordering prosedural (berbasis async) sebagaimana Rust menerapkan lifetime, atau pakai saja hal yang sudah akrab bagi orang
saya tidak setuju bahwa “asyncConcurrent(...) membingungkan”, kalau poin inti tulisan blog itu sudah diinternalisasi, sama sekali tidak membingungkan, apakah ide itu layak dipelajari adalah persoalan lain, jika nanti banyak orang benar-benar mempraktikkannya, seiring waktu kita akan tahu apakah ide ini bagus di lapangan atau tidak, dan jika kata “commutativity” dipakai untuk mengganti hal lain, justru di Zig itu bisa lebih membingungkan karena memang ada operator yang komutatif, misalnya pada f() + g(), karena penjumlahan komutatif apakah Zig jadi boleh mengeksekusinya secara paralel, kebingungan seperti itu mudah muncul, karena urutan eksekusi dan commutativity adalah hal yang sama sekali berbeda dan perlu dibedakan
secara ketat, commutativity adalah sifat yang berlaku pada operasi (biner), ketika kita mengatakan dua pernyataan async seperti connect/accept dapat dipertukarkan, pertanyaannya menjadi “terhadap operasi yang mana?”, untuk saat ini operator bind (>>=) atau .then(...) mungkin yang paling mendekati peran itu, tetapi ini masih ranah intuisi
async juga memungkinkan partial order, meskipun dua operasi harus dipensiunkan dalam urutan yang sama, urutan eksekusi nyatanya bisa berbeda, misalnya pengurangan memang tidak komutatif, tetapi kita tetap bisa menjalankan query perhitungan saldo dan query perhitungan nilai potongan secara paralel, lalu menerapkan hasilnya dalam urutan yang sesuai
hanya karena ada istilah lain yang mencakup konsep ini, bukan berarti itu menjadi kata yang lebih baik daripada “asynchrony”, kata “commutativity” terasa berantakan untuk dibaca, didengar, maupun ditulis, asynchrony jauh lebih familier
klaim bahwa ini adalah commutativity juga punya batas, jika A dan B masing-masing komutatif terhadap C maka ABC = CAB, tetapi itu tidak berarti pasti sama dengan ACB, dalam async kita membutuhkan ABC = ACB = CAB semuanya sama (kalau ada istilah matematika yang sudah ada untuk ini saya tidak tahu)
sebagai network programmer, saya sudah sangat banyak menulis concurrency, parallelism, dan kode async, dan tulisan ini terasa agak membingungkan, seolah berusaha mencari jawaban di atas abstraksi yang berlubang-lubang, jika tool atau implementasinya sendiri salah, masalahnya ya memang mudah “rusak” seperti ini, sejujurnya debugging kode multithread sendiri cukup menyenangkan, melihat orang lain sangat takut pada monster multithread justru terasa lucu