Mesin C ++ yang sedang saya kerjakan saat ini dibagi menjadi beberapa thread-Generasi besar (untuk membuat konten prosedural saya), Gameplay (untuk AI, skrip, simulasi), Fisika, dan Rendering.
Utas berkomunikasi satu sama lain melalui objek pesan kecil, yang berpindah dari utas ke utas. Sebelum melangkah, utas memproses semua pesannya yang masuk - pembaruan untuk mentransformasikan, menambah dan menghapus objek, dll. Terkadang satu utas (Generasi) akan membuat sesuatu (Seni) dan meneruskannya ke utas lain (Rendering) untuk kepemilikan permanen.
Di awal proses dan saya perhatikan beberapa hal:
Sistem pengiriman pesan rumit. Membuat jenis pesan baru berarti mensubkelas kelas pesan dasar, membuat enum baru untuk jenisnya, dan menulis logika bagaimana thread harus menafsirkan jenis pesan baru. Ini adalah peningkatan kecepatan untuk pengembangan dan rentan terhadap kesalahan ketik. (Sidenote - mengerjakan ini membuat saya menghargai betapa hebatnya bahasa dinamis!)
Apakah ada cara yang lebih baik untuk melakukan ini? Haruskah saya menggunakan sesuatu seperti boost :: bind untuk membuat ini otomatis? Saya khawatir jika saya melakukannya saya kehilangan kemampuan untuk mengatakan, mengurutkan pesan berdasarkan jenis, atau sesuatu. Tidak yakin apakah manajemen semacam itu bahkan akan diperlukan.
Poin pertama adalah penting karena utas ini sangat berkomunikasi. Membuat dan menyampaikan pesan adalah bagian besar untuk mewujudkan sesuatu. Saya ingin merampingkan sistem itu, tetapi juga terbuka untuk paradigma lain yang mungkin sama membantu. Apakah ada desain multithreaded berbeda yang harus saya pikirkan untuk membantu mempermudah ini?
Misalnya, ada beberapa sumber yang jarang ditulis, tetapi sering dibaca dari banyak utas. Haruskah saya terbuka pada gagasan memiliki data bersama, dilindungi oleh mutex, bahwa semua utas dapat mengakses?
Ini adalah pertama kalinya saya merancang sesuatu dengan multithreading dalam pikiran dari bawah ke atas. Pada tahap awal ini saya benar-benar berpikir itu berjalan sangat baik (mempertimbangkan) tetapi saya khawatir tentang penskalaan, dan efisiensi saya sendiri dalam mengimplementasikan hal-hal baru.
sumber
Jawaban:
Untuk masalah Anda yang lebih luas, pertimbangkan untuk mencari cara untuk mengurangi komunikasi antar-thread sebanyak mungkin. Lebih baik menghindari masalah sinkronisasi secara bersamaan, jika Anda bisa. Ini dapat dicapai dengan buffering ganda data Anda, memperkenalkan latensi pembaruan tunggal tetapi sangat memudahkan tugas bekerja dengan data bersama.
Selain itu, sudahkah Anda mempertimbangkan untuk tidak melakukan threading berdasarkan subsistem, tetapi sebaliknya menggunakan pemijahan utas, atau kumpulan utas untuk melakukan fork berdasarkan tugas? (lihat ini sehubungan dengan masalah spesifik Anda, untuk penggabungan ulir.) Makalah singkat ini menguraikan tujuan dan penggunaan pola kumpulan secara ringkas. Lihat jawaban informatif inijuga. Seperti dicatat di sana, thread pool meningkatkan skalabilitas sebagai bonus. Dan itu adalah "menulis sekali, gunakan di mana saja", sebagai lawan harus mendapatkan utas berbasis subsistem untuk bermain bagus setiap kali Anda menulis game atau mesin baru. Ada banyak solusi pengumpulan benang pihak ketiga yang solid di luar sana juga. Akan lebih mudah untuk memulai dengan pemijahan benang dan bekerja dengan cara Anda untuk mengumpulkan benang nanti, jika overhead pemijahan dan penghancuran benang perlu dikurangi.
sumber
Anda bertanya tentang desain multi-utas yang berbeda. Seorang teman saya memberi tahu saya tentang metode ini yang saya pikir cukup keren.
Idenya adalah bahwa akan ada 2 salinan dari setiap entitas game (boros, saya tahu). Satu salinan akan menjadi salinan saat ini, dan yang lainnya akan menjadi salinan sebelumnya. Salinan ini hanya ditulis , dan salinan yang lalu hanya dibaca dengan ketat . Ketika Anda pergi untuk memperbarui, Anda menetapkan rentang daftar entitas Anda ke sebanyak utas yang Anda inginkan. Setiap utas memiliki akses tulis ke salinan saat ini dalam rentang yang ditetapkan dan setiap utas memiliki akses baca ke semua salinan entitas sebelumnya, dan dengan demikian dapat memperbarui salinan kini yang ditugaskan menggunakan data dari salinan sebelumnya tanpa penguncian. Di antara setiap bingkai, salinan saat ini menjadi salinan masa lalu, namun Anda ingin menangani pertukaran peran.
sumber
Kami memiliki masalah yang sama, hanya dengan C #. Setelah berpikir panjang dan keras tentang kemudahan (atau ketiadaan) membuat pesan baru, yang terbaik yang bisa kita lakukan adalah membuat generator kode untuk mereka. Agak jelek, tapi bisa digunakan: hanya diberi deskripsi isi pesan, itu menghasilkan kelas pesan, enum, kode penanganan placeholder dll - semua kode ini hampir sama setiap waktu, dan sangat rawan ketik.
Saya tidak sepenuhnya senang dengan ini, tetapi lebih baik daripada menulis semua kode itu dengan tangan.
Mengenai data yang dibagikan, jawaban terbaik tentu saja "itu tergantung". Tetapi secara umum, jika beberapa data sering dibaca, dan dibutuhkan oleh banyak utas, berbagi itu layak dilakukan. Untuk keamanan utas, taruhan terbaik Anda membuatnya tidak berubah , tetapi jika itu tidak mungkin, mutex mungkin berhasil. Di C # ada
ReaderWriterLockSlim
kelas, yang dirancang khusus untuk kasus-kasus seperti itu; Saya yakin ada C ++ yang setara.Satu ide lain untuk komunikasi utas, yang mungkin memecahkan masalah pertama Anda, adalah untuk meneruskan penangan alih-alih pesan. Saya tidak yakin bagaimana cara mengatasinya dalam C ++, tetapi dalam C # Anda dapat mengirim
delegate
objek ke utas lain (seperti pada, menambahkannya ke semacam antrian pesan), dan benar-benar memanggil delegasi ini dari utas penerima. Ini memungkinkan untuk membuat pesan "ad hoc" di tempat. Saya hanya mempermainkan ide ini, tidak pernah benar-benar mencobanya dalam produksi, sehingga mungkin ternyata buruk.sumber
Saya hanya dalam tahap desain dari beberapa kode game berulir, jadi saya hanya dapat membagikan pemikiran saya, bukan pengalaman nyata apa pun. Dengan itu, saya berpikir di sepanjang baris berikut:
Saya pikir (meskipun saya tidak yakin) dalam teori ini harus berarti bahwa selama fase membaca dan memperbarui, sejumlah utas dapat berjalan secara bersamaan dengan sinkronisasi minimal. Dalam fase baca tidak ada yang menulis data yang dibagikan, sehingga tidak ada masalah konkurensi yang muncul. Fase pembaruan, yah, itu lebih sulit. Pembaruan paralel pada data yang sama akan menjadi masalah, jadi beberapa sinkronisasi dilakukan di sini. Namun, saya masih bisa menjalankan sejumlah utas pembaruan yang sewenang-wenang, asalkan mereka beroperasi pada set data yang berbeda.
Secara keseluruhan, saya pikir pendekatan ini akan cocok dengan sistem pengumpulan benang. Bagian yang bermasalah adalah:
x += 2; if (x > 5) ...
jika x dibagikan. Anda perlu membuat salinan lokal x, atau menghasilkan permintaan pembaruan, dan hanya melakukan persyaratan pada proses berikutnya. Yang terakhir akan berarti banyak tambahan kode negara-melestarikan kode boilerplate.sumber