Bagaimana saya bisa membuat pesan lewat di antara utas dalam mesin multithreaded kurang rumit?

18

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:

  1. 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.

  2. 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.

Raptormeat
sumber
6
Sebenarnya tidak ada satu pun pertanyaan yang diarahkan di sini, dan karena itu pos ini tidak cocok untuk gaya Tanya Jawab di situs ini. Saya akan merekomendasikan Anda memecah posting Anda menjadi posting yang berbeda, satu per pertanyaan, dan memfokuskan kembali pertanyaan sehingga mereka bertanya tentang masalah tertentu yang sebenarnya Anda miliki alih-alih kumpulan saran atau saran yang tidak jelas.
2
Jika Anda ingin terlibat dalam percakapan yang lebih umum, saya sarankan Anda mencoba posting ini di forum di gamedev.net . Seperti yang dikatakan Josh, karena "pertanyaan" Anda bukan satu pertanyaan spesifik, akan sangat sulit untuk mengakomodasi dalam format StackExchange.
Cypher
Terima kasih atas tanggapan kalian! Saya agak berharap seseorang dengan pengetahuan lebih mungkin memiliki sumber daya / pengalaman / paradigma tunggal yang dapat mengatasi beberapa masalah saya sekaligus. Saya mendapatkan perasaan bahwa satu ide besar dapat mensintesis berbagai masalah saya menjadi satu hal yang saya lewatkan, dan saya sedang memikirkan seseorang dengan pengalaman lebih daripada yang mungkin saya sadari bahwa ... Tapi mungkin tidak, dan bagaimanapun juga poin yang diambil !
Raptormeat
Saya mengganti nama judul Anda menjadi lebih spesifik untuk pengiriman pesan, karena pertanyaan tip "tip" menyiratkan bahwa tidak ada masalah khusus untuk dipecahkan (dan karena itu hari ini saya akan tutup sebagai "bukan pertanyaan nyata").
Tetrad
Apakah Anda yakin perlu utas terpisah untuk fisika dan gameplay? Keduanya tampaknya sangat terkait. Juga, sulit untuk mengetahui cara menawarkan saran tanpa mengetahui bagaimana masing-masing dari mereka berkomunikasi dan dengan siapa.
Nicol Bolas

Jawaban:

10

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.

Insinyur
sumber
1
Adakah rekomendasi untuk lib threading pool khusus untuk diperiksa?
imre
Terima kasih banyak atas tanggapannya. Adapun poin pertama Anda - saya pikir itu ide yang bagus dan mungkin arah saya akan pindah. Saat ini sudah cukup awal bahwa saya belum tahu apa yang perlu buffered ganda. Saya akan mengingat hal ini seiring dengan waktu. Untuk poin kedua Anda - terima kasih atas sarannya! Ya, manfaat tugas utas jelas. Saya akan membaca tautan Anda dan memikirkannya. Tidak 100% yakin apakah itu akan berhasil untuk saya / bagaimana membuatnya bekerja untuk saya tetapi saya pasti akan memikirkannya dengan serius. Terima kasih!
Raptormeat
1
@imre periksa perpustakaan Boost - mereka memiliki masa depan, yang merupakan cara yang bagus / mudah untuk mendekati hal-hal ini.
Jonathan Dickinson
5

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.

John McDonald
sumber
4

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 ReaderWriterLockSlimkelas, 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 delegateobjek 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.

Lupakan
sumber
Terima kasih untuk semua info hebatnya! Bit terakhir tentang penangan mirip dengan apa yang saya sebutkan tentang penggunaan binding atau functors untuk melewatkan fungsi. Saya agak suka ide - saya mungkin mencobanya dan melihat apakah itu menyebalkan atau mengagumkan: D Bisa mulai dengan hanya membuat kelas CallDelegateMessage dan mencelupkan kaki saya ke dalam air.
Raptormeat
1

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:

  • Sebagian besar data game harus dibagikan untuk akses hanya baca .
  • Menulis data dimungkinkan menggunakan semacam olahpesan.
  • Untuk menghindari memperbarui data saat utas lainnya membacanya, loop game memiliki dua fase berbeda: membaca dan memperbarui.
  • Dalam fase baca:
  • Semua data bersama adalah hanya-baca untuk semua utas.
  • Thread dapat menghitung hal-hal (menggunakan penyimpanan thread-lokal), dan menghasilkan permintaan pembaruan , yang pada dasarnya adalah objek perintah / pesan, ditempatkan dalam antrian, untuk diterapkan nanti.
  • Dalam fase pembaruan:
  • Semua data yang dibagikan hanya untuk menulis. Data harus diasumsikan dalam keadaan tidak diketahui / tidak stabil.
  • Di sinilah objek permintaan pembaruan diproses.

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:

  • Menyinkronkan utas pembaruan (pastikan tidak ada utas ganda mencoba memperbarui kumpulan data yang sama).
  • Memastikan bahwa dalam fase baca tidak ada utas yang secara tidak sengaja dapat menulis data bersama. Saya khawatir akan ada terlalu banyak ruang untuk kesalahan pemrograman, dan saya tidak yakin berapa banyak dari mereka dapat dengan mudah ditangkap oleh alat debug.
  • Menulis kode sedemikian rupa sehingga Anda tidak dapat bergantung pada hasil antara yang tersedia untuk segera dibaca. Artinya, Anda tidak bisa menulis 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.
imre
sumber