tugas referensi adalah atom jadi mengapa Interlocked.Exchange (ref Object, Object) diperlukan?

108

Dalam layanan web asmx multithreaded saya, saya memiliki bidang kelas _allData tipe saya sendiri SystemData yang terdiri dari sedikit List<T>dan Dictionary<T>ditandai sebagai volatile. Data sistem ( _allData) disegarkan sesekali dan saya melakukannya dengan membuat objek lain yang disebut newDatadan mengisi struktur datanya dengan data baru. Setelah selesai saya baru saja menetapkan

private static volatile SystemData _allData

public static bool LoadAllSystemData()
{
    SystemData newData = new SystemData();
    /* fill newData with up-to-date data*/
     ...
    _allData = newData.
} 

Ini harus berfungsi karena tugasnya atom dan utas yang memiliki referensi ke data lama tetap menggunakannya dan sisanya memiliki data sistem baru setelah tugas. Namun rekan saya mengatakan bahwa alih-alih menggunakan volatilekata kunci dan tugas sederhana, saya harus menggunakan InterLocked.Exchangekarena dia mengatakan bahwa pada beberapa platform tidak ada jaminan bahwa tugas referensi bersifat atomik. Apalagi: ketika saya mendeklarasikan the _allDatabidang sebagai volatilefile

Interlocked.Exchange<SystemData>(ref _allData, newData); 

menghasilkan peringatan "referensi ke kolom volatile tidak akan diperlakukan sebagai volatile" Apa yang harus saya pikirkan tentang ini?

char m
sumber

Jawaban:

180

Ada banyak pertanyaan disini. Mempertimbangkannya satu per satu:

tugas referensi adalah atom jadi mengapa Interlocked.Exchange (ref Object, Object) diperlukan?

Tugas referensi bersifat atom. Interlocked.Exchange tidak hanya melakukan tugas referensi. Itu membaca nilai saat ini dari variabel, menyembunyikan nilai lama, dan memberikan nilai baru ke variabel, semua sebagai operasi atom.

kolega saya mengatakan bahwa pada beberapa platform, tidak ada jaminan bahwa penetapan referensi bersifat atomik. Apakah kolega saya benar?

Tidak. Penetapan referensi dijamin sepenuhnya di semua platform .NET.

Rekan saya berpikir dari premis yang salah. Apakah itu berarti kesimpulan mereka salah?

Belum tentu. Kolega Anda mungkin memberi Anda nasihat yang baik untuk alasan yang buruk. Mungkin ada beberapa alasan lain mengapa Anda harus menggunakan Interlocked.Exchange. Pemrograman bebas kunci sangat sulit dan saat Anda menyimpang dari praktik mapan yang didukung oleh para ahli di bidangnya, Anda berada dalam bahaya dan mengambil risiko kondisi balapan yang paling buruk. Saya bukan ahli di bidang ini atau ahli kode Anda, jadi saya tidak dapat membuat penilaian dengan satu atau lain cara.

menghasilkan peringatan "referensi ke kolom volatile tidak akan diperlakukan sebagai volatile" Apa yang harus saya pikirkan tentang ini?

Anda harus memahami mengapa ini menjadi masalah secara umum. Itu akan mengarah pada pemahaman tentang mengapa peringatan itu tidak penting dalam kasus khusus ini.

Alasan kompilator memberikan peringatan ini adalah karena menandai bidang sebagai volatile berarti "bidang ini akan diperbarui pada beberapa utas - jangan buat kode apa pun yang menyimpan nilai bidang ini, dan pastikan bahwa setiap membaca atau menulis bidang ini tidak "dipindahkan ke depan dan ke belakang dalam waktu" melalui inkonsistensi cache prosesor. "

(Saya berasumsi bahwa Anda sudah memahami semua itu. Jika Anda tidak memiliki pemahaman terperinci tentang arti volatile dan bagaimana pengaruhnya terhadap semantik cache prosesor, maka Anda tidak memahami cara kerjanya dan tidak boleh menggunakan volatile. Program bebas kunci sangat sulit untuk dilakukan dengan benar; pastikan bahwa program Anda benar karena Anda memahami cara kerjanya, bukan secara tidak sengaja.)

Sekarang misalkan Anda membuat variabel yang merupakan alias dari bidang yang mudah menguap dengan mengirimkan ref ke bidang itu. Di dalam metode yang dipanggil, kompilator tidak memiliki alasan apa pun untuk mengetahui bahwa referensi harus memiliki semantik volatil! Kompilator dengan senang hati akan menghasilkan kode untuk metode yang gagal menerapkan aturan untuk bidang volatil, tetapi variabelnya adalah bidang volatil. Itu benar-benar dapat merusak logika bebas kunci Anda; asumsinya selalu bahwa bidang volatile selalu diakses dengan semantik volatile. Tidak masuk akal untuk memperlakukannya sebagai volatile kadang-kadang dan tidak di lain waktu; Anda harus selalu konsisten jika tidak, Anda tidak dapat menjamin konsistensi pada akses lain.

Oleh karena itu, kompilator memperingatkan ketika Anda melakukan ini, karena itu mungkin akan mengacaukan sepenuhnya logika bebas kunci yang dikembangkan dengan hati-hati.

Tentu saja, Interlocked.Exchange yang ditulis untuk mengharapkan bidang yang mudah menguap dan melakukan hal yang benar. Oleh karena itu, peringatan tersebut menyesatkan. Saya sangat menyesali ini; yang seharusnya kita lakukan adalah mengimplementasikan beberapa mekanisme di mana penulis metode seperti Interlocked.Exchange dapat meletakkan atribut pada metode yang mengatakan "metode ini yang mengambil ref memaksakan semantik volatil pada variabel, jadi tekan peringatannya". Mungkin dalam versi kompilator yang akan datang kami akan melakukannya.

Eric Lippert
sumber
1
Dari apa yang saya dengar Interlocked. Exchange juga menjamin bahwa penghalang memori dibuat. Jadi jika Anda misalnya membuat objek baru, kemudian menetapkan beberapa properti dan kemudian menyimpan objek di referensi lain tanpa menggunakan Interlocked. Exchange maka compiler mungkin mengacaukan urutan operasi tersebut, sehingga mengakses referensi kedua bukan thread- aman. Benarkah begitu? Apakah masuk akal untuk menggunakan Interlocked. Exchange adalah skenario semacam itu?
Mike
12
@ Mike: Ketika sampai pada apa yang mungkin diamati dalam situasi multithreaded low-lock, saya sama bodohnya dengan orang berikutnya. Jawabannya mungkin akan berbeda dari satu prosesor ke prosesor lainnya. Anda harus menyampaikan pertanyaan Anda kepada seorang ahli, atau membaca tentang topik tersebut jika itu menarik minat Anda. Buku Joe Duffy dan blognya adalah tempat yang baik untuk memulai. Aturan saya: jangan gunakan multithreading. Jika Anda harus, gunakan struktur data yang tidak dapat diubah. Jika Anda tidak bisa, gunakan kunci. Hanya jika Anda harus memiliki data yang dapat berubah tanpa kunci, Anda harus mempertimbangkan teknik kunci rendah.
Eric Lippert
Terima kasih atas jawaban Anda Eric. Itu memang menarik minat saya, itulah mengapa saya telah membaca buku dan blog tentang multithreading dan strategi penguncian dan juga mencoba menerapkannya di kode saya. Tapi masih banyak yang harus dipelajari ...
Mike
2
@EricLippert Antara "jangan gunakan multithreading" dan "jika Anda harus, gunakan struktur data yang tidak dapat diubah", saya akan memasukkan tingkat menengah dan paling umum dari "memiliki utas anak hanya menggunakan objek input yang dimiliki secara eksklusif dan utas induk mengkonsumsi hasilnya hanya setelah anak itu selesai ". Seperti dalam var myresult = await Task.Factory.CreateNew(() => MyWork(exclusivelyLocalStuffOrValueTypeOrCopy));.
Yohanes
1
@ John: Itu ide yang bagus. Saya mencoba memperlakukan utas seperti proses murah: mereka ada untuk melakukan pekerjaan dan menghasilkan hasil, bukan untuk berkeliling menjadi utas kontrol kedua di dalam struktur data program utama. Tetapi jika jumlah pekerjaan yang dilakukan utas sangat besar sehingga masuk akal untuk memperlakukannya seperti proses, maka saya katakan buat saja itu proses!
Eric Lippert
9

Mungkin kolega Anda salah, atau dia tahu sesuatu yang tidak diketahui oleh spesifikasi bahasa C #.

5.5 Atomitas referensi variabel :

"Pembacaan dan penulisan jenis data berikut bersifat atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, dan jenis referensi."

Jadi, Anda dapat menulis ke referensi yang mudah menguap tanpa risiko mendapatkan nilai yang rusak.

Anda tentunya harus berhati-hati dengan cara Anda memutuskan utas mana yang harus mengambil data baru, untuk meminimalkan risiko bahwa lebih dari satu utas pada satu waktu melakukannya.

Guffa
sumber
3
@ guffa: ya saya sudah membaca itu juga. ini meninggalkan pertanyaan asli "tugas referensi adalah atom jadi mengapa Interlocked.Exchange (ref Object, Object) diperlukan?" belum terjawab
char m
@ zebrabox: apa maksudmu? padahal tidak? apa yang akan kamu lakukan?
char m
@ matti: Ini diperlukan ketika Anda harus membaca dan menulis nilai sebagai operasi atom.
Guffa
Seberapa sering Anda benar-benar khawatir tentang memori yang tidak diselaraskan dengan benar di .NET? Hal-hal berat antar?
Skurmedel
1
@zebrabox: Spesifikasi tidak mencantumkan peringatan itu, memberikan pernyataan yang sangat jelas. Apakah Anda memiliki referensi untuk situasi non-memori-aligned di mana referensi membaca atau menulis gagal menjadi atom? Sepertinya itu akan melanggar bahasa yang sangat jelas di spesifikasinya.
TJ Crowder
6

Interlocked.Exchange <T>

Menetapkan variabel dari tipe T yang ditentukan ke nilai yang ditentukan dan mengembalikan nilai asli, sebagai operasi atom.

Itu mengubah dan mengembalikan nilai aslinya, tidak berguna karena Anda hanya ingin mengubahnya dan, seperti yang dikatakan Guffa, itu sudah atom.

Kecuali jika seorang profiler terbukti menjadi penghambat dalam aplikasi Anda, Anda harus mempertimbangkan kunci unsing, lebih mudah untuk memahami dan membuktikan bahwa kode Anda benar.

Guillaume
sumber
3

Iterlocked.Exchange() tidak hanya atom, tetapi juga menjaga visibilitas memori:

Fungsi sinkronisasi berikut menggunakan penghalang yang sesuai untuk memastikan pengurutan memori:

Fungsi yang masuk atau keluar dari bagian penting

Fungsi yang menandakan objek sinkronisasi

Fungsi tunggu

Fungsi saling bertautan

Masalah Sinkronisasi dan Multiprosesor

Ini berarti bahwa selain atomisitas, ini memastikan bahwa:

  • Untuk utas yang memanggilnya:
    • Tidak ada pengubahan urutan instruksi yang dilakukan (oleh kompiler, run-time atau perangkat keras).
  • Untuk semua utas:
    • Tidak ada pembacaan ke memori yang terjadi sebelum instruksi ini akan melihat perubahan yang dibuat instruksi ini.
    • Semua pembacaan setelah instruksi ini akan melihat perubahan yang dibuat oleh instruksi ini.
    • Semua penulisan ke memori setelah instruksi ini akan terjadi setelah perubahan instruksi ini mencapai memori utama (dengan mengosongkan instruksi ini, ubah ke memori utama setelah selesai dan jangan biarkan perangkat keras mengosongkan waktunya pada waktunya).
selalerer
sumber