Penerapan "bebas kunci" saat ini mengikuti pola yang sama hampir sepanjang waktu:
- membaca beberapa negara bagian dan membuat salinannya *
- ubah salinan *
- melakukan operasi yang saling bertautan
- coba lagi jika gagal
(* opsional: tergantung pada struktur data / algoritma)
Bit terakhir sangat mirip dengan spinlock. Faktanya, ini adalah spinlock dasar . :)
Saya setuju dengan @nobugz dalam hal ini: biaya operasi interlock yang digunakan dalam multi-threading tanpa kunci didominasi oleh tugas cache dan koherensi memori yang harus dijalankannya .
Apa yang Anda peroleh dengan struktur data yang "bebas kunci" adalah bahwa "kunci" Anda sangat halus . Ini mengurangi kemungkinan bahwa dua thread bersamaan mengakses "kunci" yang sama (lokasi memori).
Trik yang sering terjadi adalah Anda tidak memiliki kunci khusus - sebaliknya Anda memperlakukan misalnya semua elemen dalam larik atau semua node dalam daftar tertaut sebagai "kunci putar". Anda membaca, mengubah, dan mencoba memperbarui jika tidak ada pembaruan sejak terakhir Anda membaca. Jika ada, coba lagi.
Hal ini membuat "penguncian" Anda (oh, maaf, non-penguncian :) sangat halus, tanpa memasukkan memori tambahan atau persyaratan sumber daya.
Membuatnya lebih halus mengurangi kemungkinan menunggu. Membuatnya sesempurna mungkin tanpa memasukkan persyaratan sumber daya tambahan kedengarannya bagus, bukan?
Namun, sebagian besar kesenangan dapat datang dari memastikan pemuatan / pemesanan toko yang benar .
Berlawanan dengan intuisi seseorang, CPU bebas menyusun ulang memori baca / tulis - mereka sangat pintar, omong-omong: Anda akan kesulitan mengamati ini dari satu utas. Namun, Anda akan mengalami masalah ketika Anda mulai melakukan multi-threading pada banyak inti. Intuisi Anda akan rusak: hanya karena instruksi lebih awal dalam kode Anda, itu tidak berarti bahwa itu benar-benar akan terjadi lebih awal. CPU dapat memproses instruksi yang tidak berurutan: dan mereka terutama suka melakukan ini pada instruksi dengan akses memori, untuk menyembunyikan latensi memori utama dan memanfaatkan cache mereka dengan lebih baik.
Sekarang, pasti bertentangan dengan intuisi bahwa urutan kode tidak mengalir "dari atas ke bawah", melainkan berjalan seolah-olah tidak ada urutan sama sekali - dan dapat disebut "taman bermain setan". Saya yakin tidak mungkin memberikan jawaban yang tepat seperti apa pemesanan ulang muat / penyimpanan yang akan dilakukan. Sebaliknya, seseorang selalu berbicara dalam istilah mays and mights and can dan bersiap untuk yang terburuk. "Oh, CPU mungkin menyusun ulang pembacaan ini menjadi sebelum penulisan itu, jadi yang terbaik adalah menempatkan penghalang memori di sini, di tempat ini."
Masalah diperumit oleh fakta bahwa mays dan mights ini pun dapat berbeda di seluruh arsitektur CPU. Ini mungkin menjadi kasus, misalnya, bahwa sesuatu yang dijamin untuk tidak terjadi dalam satu arsitektur yang mungkin terjadi pada yang lain.
Untuk mendapatkan hak multi-threading "lock-free", Anda harus memahami model memori.
Namun, mendapatkan model memori dan jaminan yang benar bukanlah hal yang sepele, seperti yang ditunjukkan oleh cerita ini, di mana Intel dan AMD melakukan beberapa koreksi pada dokumentasi yang MFENCE
menyebabkan beberapa kekacauan di antara pengembang JVM . Ternyata, dokumentasi yang diandalkan developer sejak awal tidak begitu akurat.
Kunci di .NET menghasilkan penghalang memori implisit, jadi Anda aman menggunakannya (sebagian besar waktu, yaitu ... lihat misalnya kebesaran Joe Duffy - Brad Abrams - Vance Morrison ini tentang inisialisasi malas, kunci, volatil, dan memori hambatan. :) (Pastikan untuk mengikuti tautan di halaman itu.)
Sebagai bonus tambahan, Anda akan diperkenalkan dengan model memori .NET di side quest . :)
Ada juga "oldie but goldie" dari Vance Morrison: What Every Dev Must Know About Multithreaded Apps .
... dan tentu saja, seperti yang disebutkan @Eric , Joe Duffy adalah pandai membaca tentang subjek tersebut.
STM yang baik dapat mendekati penguncian yang sangat halus dan mungkin akan memberikan kinerja yang mendekati atau setara dengan implementasi buatan tangan. Salah satunya adalah STM.NET dari proyek DevLabs MS.
Jika Anda bukan seorang fanatik .NET saja, Doug Lea melakukan beberapa pekerjaan hebat di JSR-166 .
Cliff Click memiliki pandangan menarik tentang tabel hash yang tidak bergantung pada lock-striping - seperti yang dilakukan tabel hash bersamaan Java dan .NET - dan tampaknya menskalakan dengan baik ke 750 CPU.
Jika Anda tidak takut untuk menjelajah ke wilayah Linux, artikel berikut memberikan lebih banyak wawasan tentang internal arsitektur memori saat ini dan bagaimana berbagi baris-cache dapat merusak kinerja: Apa yang harus diketahui setiap programmer tentang memori .
@Ben membuat banyak komentar tentang MPI: Saya sangat setuju bahwa MPI dapat bersinar di beberapa daerah. Solusi berbasis MPI dapat lebih mudah untuk dipikirkan, lebih mudah diimplementasikan dan tidak terlalu rentan terhadap kesalahan daripada implementasi penguncian setengah matang yang mencoba untuk menjadi pintar. (Namun demikian - secara subyektif - juga berlaku untuk solusi berbasis STM.) Saya juga berani bertaruh bahwa ini adalah tahun cahaya lebih mudah untuk menulis dengan benar aplikasi terdistribusi yang layak di misalnya Erlang, seperti yang disarankan oleh banyak contoh sukses.
MPI, bagaimanapun, memiliki biaya sendiri dan masalahnya sendiri ketika dijalankan pada sistem multi-inti tunggal . Misalnya di Erlang, ada masalah yang harus diselesaikan seputar sinkronisasi penjadwalan proses dan antrian pesan .
Juga, pada intinya, sistem MPI biasanya mengimplementasikan sejenis penjadwalan N: M kooperatif untuk "proses ringan". Ini misalnya berarti bahwa ada peralihan konteks yang tak terhindarkan antara proses ringan. Memang benar bahwa ini bukan "sakelar konteks klasik" tetapi sebagian besar merupakan operasi ruang pengguna dan dapat dibuat dengan cepat - namun saya sangat meragukan bahwa ini dapat dilakukan di bawah siklus 20-200 operasi yang saling terkait . Pengalihan konteks mode pengguna adalah tentu lebih lambatbahkan di pustaka Intel McRT. Penjadwalan N: M dengan proses ringan bukanlah hal baru. LWP sudah lama ada di Solaris. Mereka ditinggalkan. Ada serat di NT. Mereka sebagian besar adalah peninggalan sekarang. Ada "aktivasi" di NetBSD. Mereka ditinggalkan. Linux memiliki pendapatnya sendiri tentang masalah penguliran N: M. Sepertinya sudah mati sekarang.
Dari waktu ke waktu, ada pesaing baru: misalnya McRT dari Intel , atau yang terbaru Penjadwalan Mode Pengguna bersama dengan ConCRT dari Microsoft.
Pada tingkat terendah, mereka melakukan apa yang dilakukan penjadwal N: M MPI. Erlang - atau sistem MPI lainnya -, mungkin mendapatkan keuntungan besar pada sistem SMP dengan mengeksploitasi UMS baru .
Saya kira pertanyaan OP bukanlah tentang manfaat dan argumen subjektif untuk / melawan solusi apa pun, tetapi jika saya harus menjawabnya, saya kira itu tergantung pada tugasnya: untuk membangun struktur data dasar berkinerja tinggi dan tingkat rendah yang berjalan pada NET juga (meskipun mereka tampaknya tidak aktif). sistem tunggal dengan banyak inti , baik teknik kunci-rendah / "bebas-kunci" atau STM akan menghasilkan hasil terbaik dalam hal kinerja dan mungkin akan mengalahkan solusi MPI kapan pun dari segi kinerja, bahkan jika kerutan di atas telah diperbaiki misalnya di Erlang.
Untuk membangun sesuatu yang cukup lebih kompleks yang berjalan pada satu sistem, saya mungkin akan memilih penguncian berbutir kasar klasik atau jika kinerja sangat diperhatikan, sebuah STM.
Untuk membangun sistem terdistribusi, sistem MPI mungkin akan menjadi pilihan yang wajar.
Perhatikan bahwa ada implementasi MPI untuk