Pada awal FORTRAN dan BASIC, pada dasarnya semua program ditulis dengan pernyataan GOTO. Hasilnya adalah kode spaghetti dan solusinya adalah pemrograman terstruktur.
Demikian pula, pointer dapat memiliki kesulitan untuk mengontrol karakteristik dalam program kami. C ++ dimulai dengan banyak petunjuk, tetapi disarankan menggunakan referensi. Perpustakaan seperti STL dapat mengurangi ketergantungan kami. Ada juga idiom untuk membuat pointer pintar yang memiliki karakteristik lebih baik, dan beberapa versi C ++ mengizinkan referensi dan kode terkelola.
Praktek pemrograman seperti pewarisan dan polimorfisme menggunakan banyak petunjuk di belakang layar (seperti halnya, sementara, pemrograman terstruktur menghasilkan kode yang diisi dengan instruksi cabang). Bahasa seperti Java menghilangkan pointer dan menggunakan pengumpulan sampah untuk mengelola data yang dialokasikan secara dinamis alih-alih bergantung pada programmer untuk mencocokkan semua pernyataan baru dan yang dihapus mereka.
Dalam bacaan saya, saya telah melihat contoh pemrograman multi-proses dan multi-thread yang tampaknya tidak menggunakan semaphores. Apakah mereka menggunakan hal yang sama dengan nama yang berbeda atau apakah mereka memiliki cara baru untuk menyusun perlindungan sumber daya dari penggunaan bersamaan?
Sebagai contoh, contoh spesifik dari sistem untuk pemrograman multithread dengan prosesor multicore adalah OpenMP. Ini mewakili wilayah kritis sebagai berikut, tanpa menggunakan semaphores, yang tampaknya tidak dimasukkan dalam lingkungan.
th_id = omp_get_thread_num();
#pragma omp critical
{
cout << "Hello World from thread " << th_id << '\n';
}
Contoh ini adalah kutipan dari: http://en.wikipedia.org/wiki/OpenMP
Atau, perlindungan yang serupa dari satu sama lain menggunakan semafor dengan fungsi wait () dan signal () mungkin terlihat seperti ini:
wait(sem);
th_id = get_thread_num();
cout << "Hello World from thread " << th_id << '\n';
signal(sem);
Dalam contoh ini, segalanya cukup sederhana, dan hanya ulasan sederhana sudah cukup untuk menunjukkan panggilan wait () dan signal () cocok dan bahkan dengan banyak konkurensi, keamanan utas disediakan. Tetapi algoritma lain lebih rumit dan menggunakan beberapa semaphores (baik biner dan berhitung) tersebar di beberapa fungsi dengan kondisi kompleks yang dapat dipanggil oleh banyak utas. Konsekuensi dari menciptakan kebuntuan atau gagal membuat hal-hal yang aman bisa sulit untuk dikelola.
Apakah sistem ini seperti OpenMP menghilangkan masalah dengan semaphores?
Apakah mereka memindahkan masalah ke tempat lain?
Bagaimana cara mengubah semaphore favorit saya menggunakan algoritma untuk tidak menggunakan semaphore lagi?
sumber
Jawaban:
Adakah teknik dan praktik pemrograman bersamaan yang tidak boleh digunakan lagi? Saya akan mengatakan ya .
Salah satu teknik pemrograman konkurensi awal yang tampaknya langka saat ini adalah pemrograman interrupt-driven . Beginilah cara kerja UNIX di tahun 1970-an. Lihat Lions Commentary tentang UNIX atau Desain Bach dari Sistem Operasi UNIX . Secara singkat, tekniknya adalah untuk menangguhkan interupsi sementara saat memanipulasi struktur data, dan kemudian mengembalikan interupsi sesudahnya. The BSD spl (9) halaman manualmemiliki contoh gaya pengkodean ini. Perhatikan bahwa interupsi berorientasi pada perangkat keras, dan kode mewujudkan hubungan implisit antara jenis interupsi perangkat keras dan struktur data yang terkait dengan perangkat keras itu. Misalnya, kode yang memanipulasi buffer I / O disk perlu menangguhkan interupsi dari perangkat keras pengontrol disk saat bekerja dengan buffer tersebut.
Gaya pemrograman ini digunakan oleh sistem operasi pada perangkat keras uniprocessor. Itu jauh lebih jarang untuk aplikasi untuk berurusan dengan interupsi. Beberapa OS mengalami gangguan perangkat lunak, dan saya pikir orang-orang mencoba membangun sistem threading atau coroutine di atasnya, tetapi ini tidak terlalu luas. (Tentu saja tidak di dunia UNIX.) Saya menduga bahwa pemrograman gaya interupsi terbatas hari ini untuk sistem tertanam kecil atau sistem waktu nyata.
Semafor adalah kemajuan dari interupsi karena mereka adalah konstruksi perangkat lunak (tidak terkait dengan perangkat keras), mereka memberikan abstraksi atas fasilitas perangkat keras, dan mereka memungkinkan multithreading dan multiprocessing. Masalah utama adalah bahwa mereka tidak terstruktur. Programmer bertanggung jawab untuk menjaga hubungan antara setiap semafor dan struktur data yang dilindunginya, secara global di seluruh program. Untuk alasan ini saya pikir semaphore telanjang jarang digunakan hari ini.
Langkah kecil ke depan lainnya adalah monitor , yang merangkum mekanisme kontrol konkurensi (kunci dan kondisi) dengan data yang dilindungi. Ini dibawa ke sistem Mesa (tautan alternatif) dan dari sana ke Jawa. (Jika Anda membaca makalah Mesa ini, Anda dapat melihat bahwa kunci dan kondisi monitor Jawa disalin hampir secara verbatim dari Mesa.) Monitor sangat membantu dalam hal programmer yang cukup teliti dan rajin dapat menulis program bersamaan dengan aman menggunakan hanya alasan lokal tentang kode dan data di dalam monitor.
Ada konstruksi perpustakaan tambahan, seperti yang ada di
java.util.concurrent
paket Java , yang mencakup berbagai struktur data yang sangat bersamaan dan konstruksi kumpulan benang. Ini dapat dikombinasikan dengan teknik tambahan seperti kurungan benang dan kekekalan efektif. Lihat Java Concurrency In Practice oleh Goetz et. Al. untuk diskusi lebih lanjut. Sayangnya, banyak programmer masih menggulirkan struktur data mereka sendiri dengan kunci dan kondisi, ketika mereka benar-benar harus menggunakan sesuatu seperti ConcurrentHashMap mana pengangkatan berat telah dilakukan oleh penulis perpustakaan.Segala sesuatu di atas memiliki beberapa karakteristik penting: mereka memiliki banyak untaian kontrol yang berinteraksi secara global, keadaan yang bisa berubah . Masalahnya adalah pemrograman dengan gaya ini masih sangat rawan kesalahan. Sangat mudah untuk kesalahan kecil tanpa disadari, mengakibatkan perilaku buruk yang sulit untuk direproduksi dan didiagnosis. Mungkin tidak ada programmer yang "cukup hati-hati dan rajin" untuk mengembangkan sistem besar dengan cara ini. Setidaknya, sangat sedikit. Jadi, saya akan mengatakan bahwa pemrograman multi-threaded dengan berbagi, keadaan bisa berubah harus dihindari jika keadaan memungkinkan.
Sayangnya tidak sepenuhnya jelas apakah itu dapat dihindari dalam semua kasus. Banyak pemrograman masih dilakukan dengan cara ini. Akan menyenangkan melihat ini digantikan oleh sesuatu yang lain. Jawaban dari Jarrod Roberson dan davidk01 menunjukkan teknik-teknik seperti data yang tidak dapat diubah, pemrograman fungsional, STM, dan penyampaian pesan. Ada banyak yang direkomendasikan, dan semuanya sedang dikembangkan secara aktif. Tapi saya tidak berpikir mereka telah sepenuhnya menggantikan keadaan yang bisa berubah yang dibagikan yang kuno dengan baik.
EDIT: inilah tanggapan saya terhadap pertanyaan spesifik di bagian akhir.
Saya tidak tahu banyak tentang OpenMP. Kesan saya adalah ini bisa sangat efektif untuk masalah yang sangat paralel seperti simulasi numerik. Tapi sepertinya itu bukan tujuan umum. Konstruksi semaphore tampaknya cukup rendah dan mengharuskan programmer untuk menjaga hubungan antara semaphore dan struktur data bersama, dengan semua masalah yang saya jelaskan di atas.
Jika Anda memiliki algoritma paralel yang menggunakan semaphores, saya tidak tahu ada teknik umum untuk mengubahnya. Anda mungkin dapat mengubahnya menjadi objek dan kemudian membangun beberapa abstraksi di sekitarnya. Tetapi jika Anda ingin menggunakan sesuatu seperti pesan-lewat, saya pikir Anda benar-benar perlu merekonseptualisasikan seluruh masalah.
sumber
Jawab pertanyaan
Konsensus umum adalah negara yang dapat diubah adalah Bad ™, dan keadaan tidak berubah adalah Good ™, yang terbukti akurat dan benar berulang kali oleh bahasa fungsional dan bahasa imperatif juga.
Masalahnya adalah bahasa imperatif arus utama tidak dirancang untuk menangani cara kerja ini, hal-hal tidak akan berubah untuk bahasa-bahasa tersebut pada malam hari. Di sinilah perbandingan untuk
GOTO
cacat. Keadaan yang tidak dapat berubah dan pesan yang lewat adalah solusi yang bagus tetapi juga bukan obat mujarab.Premis yang cacat
Pertanyaan ini didasarkan pada perbandingan dengan premis yang cacat; itu
GOTO
adalah masalah aktual dan secara universal tidak digunakan lagi oleh Dewan Desainer Bahasa Intergalatic Universal dan Serikat Rekayasa Perangkat Lunak ©! TanpaGOTO
mekanisme ASM tidak akan berfungsi sama sekali. Sama dengan premis bahwa pointer mentah adalah masalah dengan C atau C ++ dan bagaimana pointer cerdas adalah obat mujarab, mereka tidak.GOTO
bukan masalahnya, programmer adalah masalahnya. Hal yang sama berlaku untuk status yang dapat diubah bersama . Ini dengan sendirinya bukan masalah , itu adalah programer yang menggunakannya masalahnya. Jika ada cara untuk menghasilkan kode yang menggunakan status dapat dibagikan bersama dengan cara yang tidak pernah memiliki kondisi ras atau bug, maka itu tidak akan menjadi masalah. Sama seperti jika Anda tidak pernah menulis kode spaghetti denganGOTO
atau konstruksi yang tidak pasti, itu juga bukan masalah.Pendidikan adalah Panacea
Pemrogram idiot adalah apa yang ada
deprecated
, setiap bahasa populer masih memilikiGOTO
konstruk baik secara langsung maupun tidak langsung dan itu adalahbest practice
ketika benar digunakan dalam setiap bahasa yang memiliki jenis konstruksi ini.CONTOH: Java memiliki label dan
try/catch/finally
keduanya langsung berfungsi sebagaiGOTO
pernyataan.Kebanyakan programmer Java yang saya ajak bicara bahkan tidak tahu apa
immutable
artinya sebenarnya di luar mereka mengulangithe String class is immutable
dengan zombie seperti pandangan di mata mereka. Mereka pasti tidak tahu cara menggunakanfinal
kata kunci dengan benar untuk membuatimmutable
kelas. Jadi saya cukup yakin mereka tidak tahu mengapa pengiriman pesan menggunakan pesan yang tidak dapat diubah begitu hebat dan mengapa keadaan yang dapat dibagikan yang dibagi sangat tidak bagus.sumber
Kemarahan terbaru di kalangan akademis tampaknya adalah Software Transactional Memory (STM) dan berjanji untuk mengambil semua detail berbulu pemrograman multi-threaded dari tangan para programmer dengan menggunakan teknologi kompiler yang cukup pintar. Di balik layar itu masih terkunci dan semafor tetapi Anda sebagai programmer tidak perlu khawatir tentang hal itu. Manfaat dari pendekatan itu masih belum jelas dan tidak ada pesaing yang jelas.
Erlang menggunakan passing pesan dan agen untuk konkurensi dan itu adalah model yang lebih sederhana untuk digunakan dibandingkan STM. Dengan pesan yang lewat, Anda sama sekali tidak perlu khawatir tentang kunci dan semafor karena setiap agen beroperasi di mini universe sendiri sehingga tidak ada kondisi ras terkait data. Anda masih memiliki beberapa kasus tepi yang aneh, tetapi tidak terlalu rumit seperti livelocks dan deadlock. Bahasa JVM dapat menggunakan Akka dan mendapatkan semua manfaat dari penyampaian pesan dan aktor, tetapi tidak seperti Erlang, JVM tidak memiliki dukungan bawaan untuk aktor sehingga pada akhirnya Akka masih menggunakan utas dan kunci tetapi Anda sebagai programmer tidak perlu khawatir.
Model lain yang saya tahu yang tidak menggunakan kunci dan utas adalah dengan menggunakan futures yang sebenarnya hanyalah bentuk lain dari pemrograman async.
Saya tidak yakin berapa banyak teknologi ini tersedia dalam C ++ tetapi kemungkinannya adalah jika Anda melihat sesuatu yang tidak secara eksplisit menggunakan utas dan kunci maka itu akan menjadi salah satu teknik di atas untuk mengelola konkurensi.
sumber
Saya pikir ini sebagian besar tentang tingkat abstraksi. Cukup sering dalam pemrograman, berguna untuk mengabstraksi beberapa detail dengan cara yang lebih aman atau lebih mudah dibaca atau sesuatu seperti itu.
Ini berlaku untuk struktur kontrol:
if
s,for
s dan bahkantry
-catch
blok hanyalah abstraksi atasgoto
s. Abstraksi ini hampir selalu berguna, karena mereka membuat kode Anda lebih mudah dibaca. Tetapi ada beberapa kasus ketika Anda masih perlu menggunakangoto
(misalnya jika Anda menulis perakitan dengan tangan).Ini juga berlaku untuk manajemen memori: C ++ smart pointer dan GC adalah abstraksi atas raw pointer dan alokasi memori manual. Dan kadang-kadang, abstraksi ini tidak sesuai, misalnya ketika Anda benar-benar membutuhkan kinerja maksimal.
Dan hal yang sama berlaku untuk multi-threading: hal-hal seperti futures dan aktor hanyalah abstraksi atas utas, semafor, mutex, dan instruksi CAS. Abstraksi semacam itu dapat membantu Anda membuat kode Anda lebih mudah dibaca dan mereka juga membantu Anda menghindari kesalahan. Tetapi kadang-kadang, mereka tidak tepat.
Anda harus tahu alat apa yang Anda miliki dan apa kelebihan dan kekurangannya. Kemudian Anda dapat memilih abstraksi yang benar untuk tugas Anda (jika ada). Level abstraksi yang lebih tinggi tidak mengurangi level yang lebih rendah, akan selalu ada beberapa kasus di mana abstraksi tidak sesuai dan pilihan terbaik adalah menggunakan "cara lama".
sumber
Ya, tetapi Anda tidak akan bertemu dengan beberapa di antaranya.
Di masa lalu, sudah biasa menggunakan metode pemblokiran (penghalang sinkronisasi) karena menulis mutex yang baik sulit dilakukan dengan benar. Anda masih dapat melihat jejak-jejak ini dalam hal-hal seperti Menggunakan perpustakaan konkurensi modern baru-baru ini memberi Anda seperangkat alat yang lebih kaya, dan teruji secara menyeluruh untuk paralelisasi dan koordinasi antar-proses.
Demikian juga, praktik yang lebih lama adalah menulis kode yang menyiksa sehingga Anda bisa mengetahui cara memparalelkannya secara manual. Bentuk pengoptimalan (berpotensi berbahaya, jika Anda salah) juga sebagian besar telah keluar jendela dengan munculnya kompiler yang melakukan ini untuk Anda, melepaskan loop jika perlu, memprediksi cabang berikut, dll. Ini bukan teknologi baru, namun , Paling tidak 15 tahun di pasar. Mengambil keuntungan dari hal-hal seperti kolam utas juga menghindari beberapa kode yang benar-benar rumit tadi.
Jadi mungkin praktik yang sudah usang adalah menulis kode konkurensi sendiri, alih-alih menggunakan perpustakaan modern yang telah teruji dengan baik.
sumber
Grand Central Dispatch Apple adalah abstraksi elegan yang mengubah pemikiran saya tentang konkurensi. Fokusnya pada antrian membuat penerapan logika asinkron menjadi lebih mudah, dalam pengalaman saya yang sederhana.
Ketika saya memprogram di lingkungan di mana tersedia, itu telah menggantikan sebagian besar penggunaan saya utas, kunci, dan komunikasi antar-utas.
sumber
Salah satu perubahan utama pada pemrograman paralel adalah bahwa CPU jauh lebih cepat dari sebelumnya, tetapi untuk mencapai kinerja itu, memerlukan cache yang diisi dengan baik. Jika Anda mencoba menjalankan beberapa utas sekaligus bertukar di antaranya, Anda hampir selalu akan membuat cache tidak valid untuk setiap utas (yaitu, masing-masing utas membutuhkan data yang berbeda untuk beroperasi) dan Anda akhirnya mematikan kinerja lebih banyak daripada Anda terbiasa dengan CPU yang lebih lambat.
Ini adalah salah satu alasan mengapa kerangka kerja async atau berbasis tugas (misalnya Grand Central Dispatch, atau TBB Intel) lebih populer, mereka menjalankan tugas kode 1 pada suatu waktu, menyelesaikannya sebelum pindah ke yang berikutnya - namun, Anda harus mengkodekan masing-masing setiap tugas hanya membutuhkan sedikit waktu kecuali Anda ingin mengacaukan desain (mis. tugas paralel Anda benar-benar antri). Tugas intensif CPU diteruskan ke inti CPU alternatif alih-alih diproses pada utas tunggal yang memproses semua tugas. Ini juga lebih mudah untuk dikelola jika tidak ada pemrosesan multi-utas yang sebenarnya terjadi juga.
sumber