Diganggu oleh bug multithreaded


26

Di tim baru saya yang saya kelola, sebagian besar kode kami adalah platform, soket TCP, dan kode jaringan http. Semua C ++. Sebagian besar berasal dari pengembang lain yang telah meninggalkan tim. Pengembang saat ini di tim sangat cerdas, tetapi sebagian besar junior dalam hal pengalaman.

Masalah terbesar kami: bug konkurensi multi-threaded. Sebagian besar pustaka kelas kami ditulis untuk tidak sinkron dengan menggunakan beberapa kelas kumpulan utas. Metode di perpustakaan kelas sering enqueue taks berjalan lama ke kumpulan thread dari satu utas dan kemudian metode panggilan balik kelas yang dipanggil pada thread yang berbeda. Akibatnya, kami memiliki banyak bug kasus tepi yang melibatkan asumsi threading yang salah. Ini menghasilkan bug halus yang melampaui hanya memiliki bagian penting dan kunci untuk menjaga terhadap masalah konkurensi.

Apa yang membuat masalah ini lebih sulit adalah bahwa upaya untuk memperbaikinya seringkali salah. Beberapa kesalahan yang saya amati tim berusaha (atau dalam kode warisan itu sendiri) mencakup sesuatu seperti berikut:

Kesalahan umum # 1 - Memperbaiki masalah konkurensi dengan hanya mengunci data yang dibagikan, tetapi lupa tentang apa yang terjadi ketika metode tidak dipanggil dalam urutan yang diharapkan. Berikut ini contoh yang sangat sederhana:

void Foo::OnHttpRequestComplete(statuscode status)
{
    m_pBar->DoSomethingImportant(status);
}

void Foo::Shutdown()
{
    m_pBar->Cleanup();
    delete m_pBar;
    m_pBar=nullptr;
}

Jadi sekarang kita memiliki bug di mana Shutdown bisa dipanggil saat OnHttpNetworkRequestComplete terjadi. Penguji menemukan bug, menangkap dump crash, dan memberikan bug kepada pengembang. Dia pada gilirannya memperbaiki bug seperti ini.

void Foo::OnHttpRequestComplete(statuscode status)
{
    AutoLock lock(m_cs);
    m_pBar->DoSomethingImportant(status);
}

void Foo::Shutdown()
{
    AutoLock lock(m_cs);
    m_pBar->Cleanup();
    delete m_pBar;
    m_pBar=nullptr;
}

Perbaikan di atas terlihat bagus sampai Anda menyadari ada kasing tepi yang bahkan lebih halus. Apa yang terjadi jika Shutdown dipanggil sebelum OnHttpRequestComplete dipanggil kembali? Contoh dunia nyata yang dimiliki tim saya bahkan lebih kompleks, dan kasus tepi lebih sulit ditemukan selama proses peninjauan kode.

Kesalahan Umum # 2 - memperbaiki masalah jalan buntu dengan keluar dari kunci secara membabi buta, tunggu utas lainnya selesai, lalu masukkan kembali kunci - tetapi tanpa menangani case bahwa objek baru saja diperbarui oleh utas lainnya!

Kesalahan Umum # 3 - Meskipun objek dihitung, urutan shutdown "melepaskan" pointernya. Tetapi lupa untuk menunggu utas yang masih berjalan untuk melepaskan contoh itu. Dengan demikian, komponen dimatikan dengan bersih, kemudian panggilan balik palsu atau terlambat dipanggil pada objek dalam keadaan tidak mengharapkan panggilan lagi.

Ada kasus tepi lainnya, tetapi intinya adalah ini:

Pemrograman multithreaded sangat sulit, bahkan untuk orang pintar.

Saat saya mengetahui kesalahan ini, saya menghabiskan waktu untuk mendiskusikan kesalahan dengan masing-masing pengembang untuk mengembangkan perbaikan yang lebih tepat. Tetapi saya curiga mereka sering bingung tentang bagaimana menyelesaikan setiap masalah karena banyaknya kode warisan yang harus diperbaiki oleh "kanan".

Kami akan segera dikirim, dan saya yakin tambalan yang kami terapkan akan berlaku untuk rilis yang akan datang. Setelah itu, kita akan punya waktu untuk meningkatkan basis kode dan refactor jika diperlukan. Kami tidak akan punya waktu untuk menulis ulang semuanya. Dan sebagian besar kode tidak terlalu buruk. Tapi saya mencari kode refactor sehingga masalah threading dapat dihindari sama sekali.

Satu pendekatan yang saya pertimbangkan adalah ini. Untuk setiap fitur platform yang signifikan, miliki utas tunggal khusus tempat semua acara dan panggilan balik jaringan dilakukan. Mirip dengan COM apartemen threading di Windows dengan menggunakan loop pesan. Operasi pemblokiran panjang masih bisa dikirim ke utas pool kerja, tetapi panggilan balik penyelesaian dipanggil pada utas komponen. Komponen bahkan dapat berbagi utas yang sama. Kemudian semua perpustakaan kelas yang berjalan di dalam utas dapat ditulis dengan asumsi satu dunia utas.

Sebelum saya menyusuri jalan itu, saya juga sangat tertarik jika ada teknik standar lain atau pola desain untuk menangani masalah multithreaded. Dan saya harus menekankan - sesuatu di luar sebuah buku yang menjelaskan dasar-dasar mutex dan semaphore. Apa yang kamu pikirkan?

Saya juga tertarik pada pendekatan lain untuk menuju proses refactoring. Termasuk salah satu dari yang berikut:

  1. Sastra atau kertas tentang pola desain di sekitar utas. Sesuatu di luar pengantar tentang mutex dan semaphore. Kita tidak perlu paralelisme masif juga, hanya cara untuk merancang model objek untuk menangani peristiwa asinkron dari utas lainnya dengan benar .

  2. Cara membuat diagram threading dari berbagai komponen, sehingga akan mudah dipelajari dan dikembangkan solusinya. (Yaitu, setara UML untuk membahas utas di seluruh objek dan kelas)

  3. Mendidik tim pengembangan Anda tentang masalah dengan kode multithreaded.

  4. Apa yang akan kamu lakukan?


23
Beberapa orang ketika dihadapkan dengan masalah berpikir saya akan menggunakan multi threading. Sekarang mereka memiliki probe twolm
Tom Squires

20
apa yang bekerja dengan baik bagi saya adalah untuk menghilangkan sifat berubah-ubah bila memungkinkan. Ketika saya melihat objek berubah- ubah keadaan untuk lulus nilai baru, saya mencoba untuk refactor bahwa untuk melewati objek baru yang tidak berubah memegang nilai berubah sebagai gantinya. Jika inisialisasi objek dilakukan dengan aman, ini menjamin tidak adanya perlombaan data - cukup melegakan
nyamuk

3
Selamat datang di neraka multi-utas. Saya telah menulis program multi-thread / paralell selama> 20 tahun, di Ada, Occam, C ++. Tidak pernah mudah, semuanya membutuhkan pemikiran yang sangat hati-hati, dan siapa pun yang mengatakan "mudahnya hanya melakukan X" adalah orang bodoh yang tidak benar-benar mengerti apa yang sedang terjadi. Semoga berhasil.
cepat

2
Jika Anda ingin melihat konkurensi dilakukan dengan baik, gunakan Erlang! Sebenarnya apa yang Anda inginkan adalah beberapa bentuk model aktor apa-apa bersama di mana kasus sudut aneh akan dihilangkan.
Zachary K

3
@DeadMG Saya berpendapat bahwa konkurensi negara bersama secara inheren rentan terhadap kasus sudut aneh dan harus dihindari. Tapi Hei saya menulis buku tentang Erlang
Zachary K

Jawaban:


27

Kode Anda memiliki masalah penting lainnya selain dari itu. Menghapus pointer secara manual? Memanggil cleanupfungsi? Owch. Juga, sebagaimana ditunjukkan secara akurat dalam komentar pertanyaan, Anda tidak menggunakan RAII untuk kunci Anda, yang merupakan kegagalan lain yang cukup epik dan menjamin bahwa ketika DoSomethingImportantmelempar pengecualian, hal-hal buruk terjadi.

Fakta bahwa bug multithreaded ini terjadi hanyalah gejala dari masalah inti - kode Anda memiliki semantik yang sangat buruk dalam setiap situasi threading dan Anda menggunakan alat yang sama sekali tidak dapat diandalkan dan mantan idiom. Jika saya jadi Anda, saya akan kagum karena berfungsi dengan satu utas, apalagi lebih.

Kesalahan Umum # 3 - Meskipun objek dihitung, urutan shutdown "melepaskan" pointernya. Tetapi lupa untuk menunggu utas yang masih berjalan untuk melepaskan contoh itu. Dengan demikian, komponen dimatikan dengan bersih, kemudian panggilan balik palsu atau terlambat dipanggil pada objek dalam keadaan tidak mengharapkan panggilan lagi.

Inti dari penghitungan referensi adalah bahwa utas telah merilis instance itu . Karena jika tidak, maka tidak dapat dihancurkan karena utas masih memiliki referensi.

Gunakan std::shared_ptr. Ketika semua benang telah dirilis (dan tidak ada , oleh karena itu, dapat memanggil fungsi, karena mereka tidak memiliki pointer untuk itu), maka destructor disebut. Ini dijamin aman.

Kedua, gunakan pustaka threading sungguhan, seperti Intel Building Thread atau Pustaka Pola Paralel Microsoft. Menulis sendiri itu memakan waktu dan tidak dapat diandalkan dan kode Anda penuh dengan rincian threading yang tidak perlu. Melakukan kunci sendiri sama buruknya dengan melakukan manajemen memori Anda sendiri. Mereka telah menerapkan banyak idiom threading tujuan umum yang sangat berguna yang berfungsi dengan benar untuk Anda gunakan.


Ini adalah jawaban yang baik, tetapi bukan arah yang saya cari, karena menghabiskan terlalu banyak waktu untuk menilai sepotong kode sampel yang ditulis hanya untuk kesederhanaan (dan tidak mencerminkan kode asli kami dalam produk kami). Tapi saya ingin tahu tentang satu komentar yang Anda buat - "alat tidak bisa diandalkan". Apa itu alat yang tidak bisa diandalkan? Alat apa yang Anda rekomendasikan?
koncurrency

5
@koncurrency: Alat yang tidak dapat diandalkan adalah hal seperti manajemen memori manual atau menulis sinkronisasi Anda sendiri di mana secara teori ia memecahkan masalah X tetapi pada kenyataannya sangat buruk sehingga Anda dapat cukup banyak menjamin kesalahan raksasa dan satu-satunya cara mungkin bisa menyelesaikan masalah pada skala yang masuk akal adalah dengan investasi waktu pengembang yang masif dan tidak proporsional - itulah yang Anda miliki saat ini.
DeadMG

9

Poster-poster lain telah berkomentar dengan baik tentang apa yang harus dilakukan untuk memperbaiki masalah inti. Posting ini berkaitan dengan masalah yang lebih mendesak yaitu menambal kode lama dengan cukup baik untuk memberi Anda waktu untuk mengulang semuanya dengan cara yang benar. Dengan kata lain, ini bukan cara yang tepat untuk melakukan sesuatu, itu hanya cara untuk tertatih-tatih untuk saat ini.

Ide Anda untuk mengkonsolidasikan acara-acara utama adalah awal yang baik. Saya akan bertindak sejauh menggunakan utas pengiriman tunggal untuk menangani semua peristiwa sinkronisasi utama, di mana pun ada ketergantungan pesanan. Siapkan antrian pesan aman dan di mana pun Anda saat ini melakukan operasi sensitif konkurensi (alokasi, pembersihan, panggil balik, dll.), Alih-alih kirim pesan ke utas itu dan minta ia melakukan atau memicu operasi. Idenya adalah bahwa utas yang satu ini mengontrol semua mulai, berhenti, alokasi, dan pembersihan unit kerja.

Utas pengiriman tidak menyelesaikan masalah yang Anda uraikan, itu hanya mengkonsolidasikannya di satu tempat. Anda masih harus khawatir tentang peristiwa / pesan yang terjadi dalam urutan yang tidak terduga. Acara dengan run-time yang signifikan masih perlu dikirim ke utas lainnya, sehingga masih ada masalah dengan konkurensi pada data bersama. Salah satu cara untuk mengurangi hal itu adalah dengan menghindari melewatkan data dengan referensi. Jika memungkinkan, data dalam pesan pengiriman harus berupa salinan yang akan dimiliki oleh penerima. (Ini sejalan dengan pembuatan data yang tidak dapat diubah yang disebutkan orang lain.)

Keuntungan dari pendekatan pengiriman ini adalah bahwa dalam utas pengiriman Anda memiliki semacam safe-haven di mana Anda setidaknya tahu bahwa operasi tertentu sedang terjadi secara berurutan. Kerugiannya adalah ia menciptakan bottleneck dan overhead CPU tambahan. Saya sarankan jangan khawatir tentang salah satu dari hal-hal itu pada awalnya: fokus pada mendapatkan beberapa ukuran operasi yang benar terlebih dahulu dengan memindahkan sebanyak yang Anda bisa ke thread pengiriman. Kemudian lakukan beberapa profiling untuk melihat apa yang paling banyak menghabiskan waktu CPU dan mulai memindahkannya kembali dari utas pengiriman menggunakan teknik multithreading yang benar.

Sekali lagi, apa yang saya jelaskan bukanlah cara yang tepat untuk melakukan sesuatu, tetapi ini adalah proses yang dapat menggerakkan Anda ke arah yang benar dalam peningkatan yang cukup kecil untuk memenuhi tenggat waktu komersial.


+1 untuk saran menengah yang masuk akal untuk melewati tantangan yang ada.

Ya, inilah pendekatan yang saya selidiki. Anda meningkatkan poin bagus tentang kinerja.
koncurrency

Mengubah hal-hal untuk melalui satu thread pengiriman tidak terdengar seperti tambalan cepat tetapi lebih merupakan refactor besar bagi saya.
Sebastian Redl

8

Berdasarkan kode yang ditampilkan, Anda memiliki tumpukan WTF. Sangat sulit jika bukan tidak mungkin untuk secara bertahap memperbaiki aplikasi multi-utas yang ditulis dengan buruk. Beri tahu pemiliknya bahwa aplikasi tidak akan dapat diandalkan tanpa pengerjaan ulang yang signifikan. Beri mereka perkiraan berdasarkan inspeksi dan pengerjaan ulang setiap bit kode yang berinteraksi dengan objek bersama. Pertama beri mereka perkiraan untuk inspeksi. Kemudian Anda bisa memberikan perkiraan untuk pengerjaan ulang.

Ketika Anda mengerjakan ulang kode, Anda harus merencanakan untuk menulis kode sehingga terbukti benar. Jika Anda tidak tahu bagaimana cara melakukannya, temukan seseorang yang melakukannya, atau Anda akan berakhir di tempat yang sama.


Baca saja ini sekarang, setelah jawaban saya terangkat. Hanya ingin mengatakan bahwa saya suka kalimat pengantar :)
back2dos

7

Jika Anda memiliki waktu untuk mendedikasikan untuk refactoring aplikasi Anda, saya akan menyarankan Anda untuk melihat model aktor (lihat misalnya Theron , Casablanca , libcppa , CAF untuk implementasi C ++).

Aktor adalah objek yang berjalan secara bersamaan dan berkomunikasi satu sama lain hanya menggunakan pertukaran pesan asinkron. Jadi, semua masalah manajemen thread, mutex, deadlock, dll, ditangani oleh perpustakaan implementasi aktor dan Anda dapat berkonsentrasi pada penerapan perilaku objek Anda (aktor), yang bermuara untuk mengulangi loop

  1. Terima pesan
  2. Lakukan perhitungan
  3. Kirim pesan / buat / bunuh aktor lain.

Satu pendekatan untuk Anda adalah dengan membaca topik terlebih dahulu, dan mungkin melihat satu atau dua perpustakaan untuk melihat apakah model aktor dapat diintegrasikan dalam kode Anda.

Saya telah menggunakan (versi sederhana) model ini dalam proyek saya selama beberapa bulan sekarang dan saya kagum dengan betapa kuatnya itu.


1
Perpustakaan Akka untuk Scala adalah implementasi yang bagus dari ini yang banyak berpikir tentang cara membunuh aktor orang tua ketika anak-anak meninggal, atau sebaliknya. Saya tahu ini bukan C ++, tapi patut dilihat: akka.io
GlenPeterson

1
@ GlenPeterson: Terima kasih, saya tahu tentang akka (yang saya anggap solusi paling menarik saat ini, dan bekerja dengan Java dan Scala) tetapi pertanyaannya membahas C ++ secara khusus. Kalau tidak, orang bisa mempertimbangkan Erlang. Saya kira di Erlang semua sakit kepala pemrograman multi-threading hilang untuk selamanya. Tapi mungkin kerangka kerja seperti akka datang sangat dekat.
Giorgio

"Kurasa di Erlang semua sakit kepala pemrograman multi-threading hilang untuk selamanya." Saya pikir mungkin ini sedikit berlebihan. Atau jika benar, maka kinerjanya mungkin kurang. Saya tahu Akka tidak bekerja dengan C ++, hanya mengatakan bahwa itu terlihat seperti state-of-the-art untuk mengelola beberapa utas. Namun, ini bukan thread-safe. Anda masih bisa melewati keadaan yang bisa berubah di antara aktor dan menembak diri sendiri di kaki.
GlenPeterson

Saya bukan ahli Erlang tetapi AFAIK masing-masing aktor dieksekusi secara terpisah dan pesan-pesan abadi dipertukarkan. Jadi Anda benar-benar tidak harus berurusan dengan utas dan berbagi keadaan yang bisa berubah-ubah sama sekali. Kinerja mungkin lebih rendah dari C ++, tetapi ini selalu terjadi ketika Anda meningkatkan level abstraksi (Anda meningkatkan waktu eksekusi tetapi mengurangi waktu pengembangan).
Giorgio

Bisakah para downvoter memberikan komentar dan menyarankan bagaimana saya dapat meningkatkan jawaban ini?
Giorgio

6

Kesalahan umum # 1 - Memperbaiki masalah konkurensi dengan hanya mengunci data yang dibagikan, tetapi lupa tentang apa yang terjadi ketika metode tidak dipanggil dalam urutan yang diharapkan. Berikut ini contoh yang sangat sederhana:

Kesalahan di sini bukanlah "lupa", tetapi "tidak memperbaikinya". Jika Anda memiliki hal-hal yang terjadi dalam urutan yang tidak terduga, Anda memiliki masalah. Anda harus menyelesaikannya alih-alih mencoba mengatasinya (menampar kunci pada sesuatu biasanya merupakan penyelesaian).

Anda harus mencoba untuk menyesuaikan model aktor / pengiriman pesan ke tingkat tertentu dan memiliki pemisahan perhatian. Peran Foojelas untuk menangani semacam komunikasi HTTP. Jika Anda ingin merancang sistem Anda untuk melakukan hal ini secara paralel, itu adalah lapisan di atas yang harus menangani siklus objek dan akses sinkronisasi yang sesuai.

Mencoba agar sejumlah utas beroperasi pada data yang bisa berubah yang sama itu sulit. Tapi itu juga jarang diperlukan. Semua kasus umum yang menuntut ini, telah diabstraksikan menjadi konsep yang lebih mudah dikelola dan diimplementasikan beberapa kali untuk setiap bahasa imperatif utama. Anda hanya perlu menggunakannya.


2

Masalah Anda cukup buruk, tetapi tipikal penggunaan C ++ yang buruk. Ulasan kode akan memperbaiki beberapa masalah ini. 30 menit, satu set bola mata menghasilkan 90% dari hasil. (Kutipan untuk ini dapat digunakan di google)

# 1 Masalah Anda harus memastikan ada hierarki kunci yang ketat untuk mencegah kebuntuan penguncian Anda.

Jika Anda mengganti Kunci Otomatis dengan pembungkus dan makro Anda bisa melakukan ini.

Simpan peta global kunci statis yang dibuat di belakang bungkus Anda. Anda menggunakan makro untuk memasukkan informasi nama file dan nomor baris ke konstruktor pembungkus kunci otomatis.

Anda juga memerlukan grafik dominator statis.

Sekarang di dalam kunci Anda harus memperbarui grafik dominator, dan jika Anda mendapatkan perubahan pemesanan Anda menyatakan kesalahan dan batalkan.

Setelah pengujian ekstensif, Anda dapat menyingkirkan sebagian besar deadlock laten.

Kode dibiarkan sebagai latihan untuk siswa.

Masalah # 2 kemudian akan hilang (kebanyakan)

Solusi aktual arsip Anda akan berfungsi. Saya telah menggunakannya sebelumnya dalam sistem misi dan kehidupan. Pandangan saya tentang ini adalah ini

  • Berikan benda-benda yang tidak bisa diubah atau buat salinannya sebelum lewat.
  • Jangan berbagi data melalui variabel publik atau getter.

  • Peristiwa eksternal masuk melalui pengiriman multithreaded ke antrian dilayani oleh satu utas. Sekarang Anda dapat menyortir alasan tentang penanganan Acara.

  • Perubahan data yang melintasi benang menjadi antrian yang aman, ditangani oleh satu utas. Buat langganan. Sekarang Anda dapat mengurutkan alasan tentang aliran data.

  • Jika data Anda perlu lintas kota, publikasikan ke antrian data. Itu akan menyalinnya dan meneruskannya kepada pelanggan secara serempak. Juga istirahat semua ketergantungan data dalam program.

Ini cukup banyak model aktor dengan harga murah. Tautan Giorgio akan membantu.

Akhirnya, masalah Anda dengan objek mati.

Ketika Anda menghitung referensi, Anda telah memecahkan 50%. 50% lainnya adalah untuk referensi jumlah panggilan balik. Berikan referensi kepada pemegang callback. Panggilan shutdown kemudian harus menunggu nol hitungan pada refcount. Tidak menyelesaikan grafik objek yang rumit; itu masuk ke pengumpulan sampah nyata. (Yang merupakan motivasi di Jawa untuk tidak membuat janji tentang kapan atau jika selesai () akan dipanggil; untuk membuat Anda keluar dari pemrograman seperti itu.)


2

Untuk penjelajah masa depan: untuk melengkapi jawaban tentang model aktor, saya ingin menambahkan CSP ( mengkomunikasikan proses sekuensial ), dengan anggukan pada keluarga besar kalkulus proses. CSP mirip dengan model aktor, tetapi berpisah secara berbeda. Anda masih memiliki banyak utas, tetapi mereka berkomunikasi melalui saluran tertentu, bukan secara khusus satu sama lain, dan kedua proses harus siap untuk masing-masing mengirim dan menerima sebelum keduanya terjadi. Ada juga bahasa formal untuk membuktikan kode CSP yang benar. Saya masih beralih ke menggunakan CSP banyak, tapi saya sudah menggunakannya dalam beberapa proyek selama beberapa bulan, sekarang, dan ini sangat disederhanakan.

University of Kent memiliki implementasi C ++ ( https://www.cs.kent.ac.uk/projects/ofa/c++csp/ , dikloning di https://github.com/themasterchef/cppcsp2 ).


1

Literatur atau kertas tentang pola desain di sekitar utas Sesuatu di luar pengantar tentang mutex dan semaphore. Kita tidak perlu paralelisme masif juga, hanya cara untuk merancang model objek untuk menangani peristiwa asinkron dari utas lainnya dengan benar.

Saya saat ini membaca ini dan menjelaskan semua masalah yang Anda dapatkan dan bagaimana cara menghindarinya, dalam C ++ (menggunakan pustaka threading baru tapi saya pikir penjelasan global berlaku untuk kasus Anda): http: //www.amazon. com / C-Concurrency-Action-Practical-Multithreading / dp / 1933988770 / ref = sr_1_1? yaitu = UTF8 & qid = 1337934534 & sr = 8-1

Cara membuat diagram threading dari berbagai komponen, sehingga akan mudah dipelajari dan dikembangkan solusinya. (Yaitu, setara UML untuk membahas utas di seluruh objek dan kelas)

Saya pribadi menggunakan UML yang disederhanakan dan hanya berasumsi bahwa pesan dilakukan secara tidak sinkron. Juga, ini benar antara "modul" tetapi di dalam modul saya tidak ingin harus tahu.

Mendidik tim pengembangan Anda tentang masalah dengan kode multithreaded.

Buku ini akan membantu, tetapi saya pikir latihan / prototyping dan mentor yang berpengalaman akan lebih baik.

Apa yang akan kamu lakukan?

Saya benar-benar akan menghindari orang yang tidak memahami masalah konkurensi yang bekerja di proyek. Tapi saya kira Anda tidak bisa melakukan itu, jadi dalam kasus spesifik Anda, selain mencoba memastikan tim menjadi lebih berpendidikan, saya tidak tahu.


Terima kasih untuk saran buku. Saya mungkin akan mengambilnya.
koncurrency

Threading sangat sulit. Tidak setiap programmer mampu menghadapi tantangan ini. Di dunia bisnis, setiap kali saya melihat utas digunakan, mereka dikelilingi oleh kunci sedemikian rupa sehingga tidak ada dua utas yang dapat berjalan secara bersamaan. Ada aturan yang bisa Anda ikuti untuk membuatnya lebih mudah, tetapi masih sulit.
GlenPeterson

@ GlenPeterson Setuju, sekarang saya memiliki lebih banyak pengalaman (karena jawaban ini) saya menemukan bahwa kita membutuhkan abstraksi yang lebih baik untuk membuatnya dapat dikelola dan untuk mencegah berbagi data. Untungnya, perancang bahasa tampaknya bekerja keras dalam hal ini.
Klaim

Saya benar-benar terkesan dengan Scala, khususnya untuk membawa manfaat pemrograman fungsional dari ketidakteraturan, efek samping minimal ke Jawa, yang merupakan keturunan langsung C ++. Ini berjalan di Java Virtual Machine, jadi mungkin tidak memiliki kinerja yang Anda butuhkan. Buku Joshua Bloch, "Java Efektif" adalah tentang meminimalkan mutabilitas, membuat antarmuka kedap udara, dan keamanan utas. Meskipun berbasis Java, saya yakin Anda bisa menerapkan 80-90% untuk C ++. Mempertanyakan mutabilitas dan status bersama (atau mutabilitas OF state bersama) dalam ulasan kode Anda mungkin merupakan langkah pertama yang baik untuk Anda.
GlenPeterson

1

Anda sudah berada di jalan dengan mengakui masalah dan secara aktif mencari solusi. Inilah yang akan saya lakukan:

  • Duduk dan rancang model threading untuk aplikasi Anda. Ini adalah dokumen yang menjawab pertanyaan seperti: Jenis utas apa yang Anda miliki? Hal-hal apa yang harus dilakukan di utas mana? Apa jenis pola sinkronisasi yang harus Anda gunakan? Dengan kata lain, itu harus menggambarkan "aturan keterlibatan" ketika memerangi masalah multithreading.
  • Gunakan alat analisis utas untuk memeriksa kesalahan kode Anda. Valgrind memiliki pemeriksa ulir yang disebut Helgrind yang pandai melihat hal-hal seperti keadaan bersama yang dimanipulasi tanpa sinkronisasi yang tepat. Pasti ada alat bagus lain di luar sana, pergi mencarinya.
  • Pertimbangkan bermigrasi jauh dari C ++. C ++ adalah mimpi buruk untuk menulis program bersamaan. Pilihan pribadi saya adalah Erlang , tapi itu masalah selera.

8
Pasti -1 untuk bit terakhir. Tampaknya kode OP menggunakan alat yang paling primitif dan bukan alat C ++ yang sebenarnya.
DeadMG

2
Saya tidak setuju. Konkurensi dalam C ++ adalah mimpi buruk bahkan jika Anda menggunakan mekanisme dan alat C ++ yang tepat. Dan harap dicatat bahwa saya memilih kata-kata " pertimbangkan ". Saya sepenuhnya mengerti bahwa itu mungkin bukan alternatif yang realistis, tetapi tetap dengan C ++ tanpa mempertimbangkan alternatifnya hanya konyol.
JesperE

4
@JesperE - maaf, tapi tidak. Konkurensi dalam C ++ hanyalah mimpi buruk jika Anda membuatnya dengan menjadi terlalu rendah. Gunakan abstraksi threading yang tepat dan tidak lebih buruk dari bahasa atau runtime lainnya. Dan dengan struktur aplikasi yang tepat, sebenarnya cukup semudah apa pun yang pernah saya lihat.
Michael Kohne

2
Di mana saya bekerja, saya yakin kita memiliki struktur aplikasi yang tepat, menggunakan abstraksi threading yang benar, dan sebagainya. Meskipun demikian, kami telah menghabiskan berjam-jam selama bertahun-tahun bug debugging yang tidak akan muncul dalam bahasa yang dirancang untuk konkurensi. Tetapi saya merasa bahwa kita harus setuju untuk tidak setuju tentang ini.
JesperE

1
@JesperE: Saya setuju dengan Anda. Model Erlang (yang ada implementasi untuk Scala / Java, Ruby, dan, sejauh yang saya tahu, juga untuk C ++) jauh lebih kuat daripada pengkodean langsung dengan utas.
Giorgio

1

Melihat contoh Anda: Segera setelah Foo :: Shutdown mulai dijalankan, Anda tidak mungkin memanggil OnHttpRequestComplete untuk menjalankannya lagi. Itu tidak ada hubungannya dengan implementasi apa pun, hanya saja tidak bisa.

Anda juga bisa berpendapat bahwa Foo :: Shutdown seharusnya tidak bisa dipanggil saat panggilan ke OnHttpRequestComplete berjalan (pasti benar) dan mungkin tidak jika panggilan ke OnHttpRequestComplete masih beredar.

Hal pertama yang harus dilakukan adalah tidak mengunci dll, tetapi logika dari apa yang diizinkan atau tidak. Model sederhana adalah bahwa kelas Anda mungkin memiliki nol atau lebih permintaan tidak lengkap, nol atau lebih penyelesaian yang belum dipanggil, nol atau lebih penyelesaian yang sedang berjalan, dan bahwa objek Anda ingin dimatikan atau tidak.

Foo :: Shutdown akan diharapkan untuk menyelesaikan menjalankan penyelesaian, untuk menjalankan permintaan yang tidak lengkap ke titik di mana mereka dapat dimatikan jika memungkinkan, untuk tidak mengizinkan penyelesaian lagi untuk dimulai, untuk tidak mengizinkan lebih banyak permintaan untuk dimulai.

Apa yang perlu Anda lakukan: Tambahkan spesifikasi ke fungsi Anda mengatakan dengan tepat apa yang akan mereka lakukan. (Misalnya, memulai permintaan http mungkin gagal setelah Shutdown dipanggil). Dan kemudian tulis fungsi Anda sehingga memenuhi spesifikasi.

Kunci paling baik digunakan hanya untuk jumlah waktu sekecil mungkin untuk mengendalikan modifikasi variabel bersama. Jadi, Anda mungkin memiliki variabel "PerformShutDown" yang dilindungi oleh Kunci.


0

Apa yang akan kamu lakukan?

Sejujurnya; Saya akan lari, cepat.

Masalah konkurensi yang jahat . Sesuatu dapat bekerja dengan sempurna selama berbulan-bulan dan kemudian (karena waktu spesifik beberapa hal) tiba-tiba meledak di wajah pelanggan, tanpa ada cara untuk mengetahui apa yang terjadi, tidak ada harapan untuk melihat laporan bug yang bagus (dapat diproduksi ulang) dan tidak mungkin bahkan untuk memastikan itu bukan kesalahan perangkat keras yang tidak ada hubungannya dengan perangkat lunak.

Menghindari masalah konkurensi harus dimulai selama fase desain, dimulai dengan bagaimana Anda akan melakukannya ("urutan kunci global", model aktor, ...). Itu bukan sesuatu yang Anda coba perbaiki dalam kepanikan gila dengan harapan bahwa semuanya tidak akan hancur sendiri setelah rilis yang akan datang.

Perhatikan bahwa saya tidak bercanda di sini. Kata-kata Anda sendiri (" Sebagian besar berasal dari pengembang lain yang telah meninggalkan tim. Pengembang saat ini dalam tim sangat cerdas, tetapi sebagian besar junior dalam hal pengalaman. ") Menunjukkan bahwa semua pengalaman yang dialami orang telah melakukan apa yang saya lakukan. sedang menyarankan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.