Java dan C # memberikan keamanan memori dengan memeriksa batas array dan pointer dereferences.
Mekanisme apa yang dapat diimplementasikan ke dalam bahasa pemrograman untuk mencegah kemungkinan kondisi balapan dan kebuntuan?
Java dan C # memberikan keamanan memori dengan memeriksa batas array dan pointer dereferences.
Mekanisme apa yang dapat diimplementasikan ke dalam bahasa pemrograman untuk mencegah kemungkinan kondisi balapan dan kebuntuan?
Jawaban:
Ras terjadi ketika Anda memiliki alias secara simultan dari suatu objek dan, setidaknya, salah satu alias bermutasi.
Jadi, untuk mencegah balapan, Anda harus membuat satu atau lebih kondisi ini tidak benar.
Berbagai pendekatan menangani berbagai aspek. Pemrograman fungsional menekankan keabadian yang menghilangkan mutabilitas. Mengunci / atom menghapus simultanitas. Jenis affine menghapus aliasing (Rust menghapus aliasing yang bisa berubah). Model aktor biasanya menghapus alias.
Anda dapat membatasi objek yang dapat alias agar lebih mudah untuk memastikan bahwa kondisi di atas dihindari. Di situlah saluran dan / atau gaya penyampaian pesan masuk. Anda tidak dapat alias memori sewenang-wenang, hanya akhir dari saluran atau antrian yang diatur untuk bebas ras. Biasanya dengan menghindari simultan yaitu kunci atau atom.
Kelemahan dari berbagai mekanisme ini adalah mereka membatasi program yang dapat Anda tulis. Semakin tumpul batasannya, semakin sedikit programnya. Jadi tidak ada aliasing atau tidak ada mutabilitas yang bekerja, dan mudah dipikirkan, tetapi sangat terbatas.
Itu sebabnya Rust menyebabkan kehebohan seperti itu. Ini adalah bahasa teknik (tidak seperti yang akademis) yang mendukung aliasing dan kemampuan berubah tetapi memiliki kompiler memeriksa bahwa mereka tidak terjadi secara bersamaan. Meskipun bukan yang ideal, itu memungkinkan kelas yang lebih besar dari program yang ditulis dengan aman daripada banyak pendahulunya.
sumber
Sangat penting untuk terlebih dahulu memikirkan bagaimana C # dan Java melakukan ini. Mereka melakukannya dengan mengubah perilaku yang tidak terdefinisi dalam C atau C ++ menjadi perilaku yang didefinisikan: crash program . Pengecualian null dan indeks array tidak boleh ditangkap dalam program C # atau Java yang benar; mereka seharusnya tidak terjadi sejak awal karena program seharusnya tidak memiliki bug itu.
Tapi saya pikir bukan itu yang Anda maksud dengan pertanyaan Anda! Kita bisa dengan mudah menulis runtime "deadlock safe" yang secara berkala memeriksa untuk melihat apakah ada n thread yang saling menunggu satu sama lain dan mengakhiri program jika itu terjadi, tapi saya tidak berpikir itu akan memuaskan Anda.
Masalah berikutnya yang kita hadapi dengan pertanyaan Anda adalah bahwa "kondisi ras", tidak seperti deadlock, sulit dideteksi. Ingat, apa yang kita kejar dalam keselamatan benang bukanlah menghilangkan ras . Yang kami kejar adalah membuat program ini benar, tidak peduli siapa yang memenangkan lomba ! Masalah dengan kondisi balapan bukanlah bahwa dua utas berjalan dalam urutan yang tidak ditentukan dan kita tidak tahu siapa yang akan menyelesaikan terlebih dahulu. Masalah dengan kondisi balapan adalah bahwa pengembang lupa bahwa beberapa pesanan penyelesaian ulah dimungkinkan dan gagal memperhitungkan kemungkinan itu.
Jadi pertanyaan Anda pada dasarnya bermuara pada "adakah cara agar bahasa pemrograman dapat memastikan bahwa program saya benar?" dan jawaban untuk pertanyaan itu adalah, dalam praktiknya, tidak.
Sejauh ini saya hanya mengkritik pertanyaan Anda. Biarkan saya mencoba untuk pindah persneling di sini dan mengatasi semangat pertanyaan Anda. Adakah pilihan yang bisa dibuat oleh perancang bahasa yang akan mengurangi situasi mengerikan yang kita alami dengan multithreading?
Situasinya benar-benar mengerikan! Mendapatkan kode multithreaded dengan benar, terutama pada arsitektur model memori yang lemah, sangat, sangat sulit. Penting untuk memikirkan mengapa itu sulit:
Jadi ada cara yang jelas bahwa perancang bahasa dapat membuat segalanya lebih baik. Abaikan kemenangan kinerja prosesor modern . Buat semua program, bahkan yang multi-utas, memiliki model memori yang sangat kuat. Ini akan membuat program multithread banyak, berkali-kali lebih lambat, yang bekerja secara langsung dengan alasan memiliki program multithreaded di tempat pertama: untuk peningkatan kinerja.
Meskipun mengesampingkan model memori, ada alasan lain mengapa multithreading sulit:
Poin terakhir itu memberikan penjelasan lebih lanjut. Yang dimaksud dengan "composable" adalah:
Misalkan kita ingin menghitung int yang diberi double. Kami menulis implementasi perhitungan yang benar:
Misalkan kita ingin menghitung string yang diberikan int:
Sekarang jika kita ingin menghitung string yang diberi ganda:
G dan F dapat disusun menjadi solusi yang tepat untuk masalah yang lebih kompleks.
Tetapi kunci tidak memiliki properti ini karena kebuntuan. Metode yang benar M1 yang mengambil kunci dalam urutan L1, L2, dan metode yang benar M2 yang mengambil kunci dalam urutan L2, L1, tidak bisa keduanya digunakan dalam program yang sama tanpa membuat program yang salah. Kunci membuatnya sehingga Anda tidak bisa mengatakan "setiap metode individu benar, sehingga semuanya benar".
Jadi, apa yang bisa kita lakukan, sebagai perancang bahasa?
Pertama, jangan ke sana. Beberapa utas kontrol dalam satu program adalah ide yang buruk, dan berbagi memori di utas adalah ide yang buruk, jadi jangan memasukkannya ke dalam bahasa atau runtime.
Ini tampaknya bukan pemula.
Mari kita mengalihkan perhatian kita ke pertanyaan yang lebih mendasar: mengapa kita memiliki banyak utas? Ada dua alasan utama, dan mereka sering digabungkan ke dalam hal yang sama, meskipun mereka sangat berbeda. Mereka menyatu karena mereka berdua tentang mengelola latensi.
Ide buruk. Sebagai gantinya, gunakan asynchrony berulir tunggal melalui coroutine. C # melakukan ini dengan indah. Jawa, tidak begitu baik. Tapi ini adalah cara utama bahwa tanaman perancang bahasa saat ini membantu menyelesaikan masalah threading. The
await
operator dalam C # (terinspirasi oleh F # alur kerja asynchronous dan prior art lainnya) sedang dimasukkan ke dalam semakin banyak bahasa.Desainer bahasa dapat membantu dengan menciptakan fitur bahasa yang bekerja dengan baik dengan paralelisme. Pikirkan tentang bagaimana LINQ diperluas secara alami ke PLINQ, misalnya. Jika Anda orang yang bijaksana dan membatasi operasi TPL Anda untuk operasi yang terikat CPU yang sangat paralel dan tidak berbagi memori, Anda bisa mendapatkan kemenangan besar di sini.
apa lagi yang bisa kita lakukan?
C # tidak memungkinkan Anda untuk menunggu di kunci, karena itu adalah resep untuk kebuntuan. C # tidak memungkinkan Anda untuk mengunci tipe nilai karena itu selalu hal yang salah untuk dilakukan; Anda mengunci kotak, bukan nilainya. C # memperingatkan Anda jika Anda alias volatile, karena alias tidak memaksakan mengakuisisi / melepaskan semantik. Ada banyak cara kompiler dapat mendeteksi masalah umum dan mencegahnya.
C # dan Java membuat kesalahan desain yang sangat besar dengan memungkinkan Anda menggunakan objek referensi apa pun sebagai monitor. Itu mendorong semua jenis praktik buruk yang membuatnya lebih sulit untuk melacak kebuntuan dan lebih sulit untuk mencegahnya secara statis. Dan itu membuang byte di setiap header objek. Monitor harus diminta berasal dari kelas monitor.
STM adalah ide yang indah, dan saya telah bermain-main dengan implementasi mainan di Haskell; ini memungkinkan Anda untuk menyusun solusi yang benar secara lebih elegan dari bagian yang benar daripada solusi berbasis kunci. Namun, saya tidak cukup tahu tentang perincian untuk mengatakan mengapa itu tidak dapat dibuat untuk bekerja pada skala; tanya Joe Duffy lain kali Anda melihatnya.
Ada banyak penelitian dalam proses bahasa berbasis kalkulus dan saya tidak mengerti ruang itu dengan sangat baik; coba baca beberapa makalah tentang itu sendiri dan lihat apakah Anda mendapatkan wawasan.
Setelah saya bekerja di Microsoft pada Roslyn saya bekerja di Coverity, dan salah satu hal yang saya lakukan adalah mendapatkan alat analisis menggunakan Roslyn. Dengan memiliki analisis leksikal, sintaksis, dan semantik yang akurat yang disediakan oleh Microsoft, kami kemudian dapat berkonsentrasi pada kerja keras penulisan detektor yang menemukan masalah umum multithreading.
Alasan mendasar mengapa kita memiliki ras dan kebuntuan dan semua itu adalah karena kita sedang menulis program yang mengatakan apa yang harus dilakukan , dan ternyata kita semua omong kosong dalam menulis program penting; komputer melakukan apa yang Anda katakan, dan kami memerintahkannya untuk melakukan hal yang salah. Banyak bahasa pemrograman modern lebih dan lebih banyak tentang pemrograman deklaratif: katakan apa hasil yang Anda inginkan, dan biarkan kompiler mencari cara yang efisien, aman, benar untuk mencapai hasil itu. Sekali lagi, pikirkan LINQ; kami ingin Anda mengatakan
from c in customers select c.FirstName
, yang mengekspresikan niat . Biarkan kompiler mengetahui cara menulis kode.Algoritma pembelajaran mesin jauh lebih baik dalam beberapa tugas daripada algoritma kode tangan, meskipun tentu saja ada banyak pengorbanan termasuk kebenaran, waktu yang dibutuhkan untuk melatih, bias yang diperkenalkan oleh pelatihan yang buruk, dan sebagainya. Tetapi sangat mungkin bahwa banyak sekali tugas yang saat ini kami kode "dengan tangan" akan segera menerima solusi yang dihasilkan mesin. Jika manusia tidak menulis kode, mereka tidak menulis bug.
Maaf itu agak mengoceh di sana; ini adalah topik besar dan sulit dan tidak ada konsensus yang jelas telah muncul di komunitas PL dalam 20 tahun saya telah mengikuti kemajuan dalam ruang masalah ini.
sumber