Berbicara dalam konteks gim berbasis openGL renderer:
Mari kita asumsikan ada dua utas:
Memperbarui logika dan fisika game, dll. Untuk objek dalam game
Membuat panggilan draw openGL untuk setiap objek game berdasarkan data dalam objek game (utas 1 yang terus diperbarui)
Kecuali jika Anda memiliki dua salinan dari masing-masing objek game dalam keadaan saat ini dari permainan Anda harus menjeda Thread 1 sementara Thread 2 melakukan draw draw jika tidak, objek game akan diperbarui di tengah-tengah draw draw untuk objek tersebut, yang tidak diinginkan!
Tetapi menghentikan thread 1 untuk membuat panggilan draw dengan aman dari thread 2 membunuh seluruh tujuan multithreading / concurrency
Apakah ada pendekatan yang lebih baik untuk ini selain menggunakan ratusan atau ribuan atau menyinkronkan objek / pagar sehingga arsitektur multicore dapat dieksploitasi untuk kinerja?
Saya tahu saya masih bisa menggunakan multithreading untuk memuat tekstur dan mengkompilasi shader untuk objek yang belum menjadi bagian dari kondisi gim saat ini, tetapi bagaimana cara melakukannya untuk objek yang aktif / terlihat tanpa menyebabkan konflik dengan menggambar dan memperbarui?
Bagaimana jika saya menggunakan kunci sinkronisasi terpisah di setiap objek game? Dengan cara ini utas apa pun hanya akan memblokir pada satu objek (case ideal) daripada untuk seluruh siklus pembaruan / undian! Tetapi seberapa mahal mengambil kunci pada setiap objek (game mungkin memiliki seribu objek)?
sumber
Jawaban:
Pendekatan yang Anda jelaskan, menggunakan kunci, akan sangat tidak efisien dan kemungkinan besar lebih lambat daripada menggunakan utas tunggal. Pendekatan lain untuk menyimpan salinan data di setiap utas mungkin akan bekerja dengan baik "kecepatan-bijaksana", tetapi dengan biaya memori yang rumit dan kompleksitas kode untuk menjaga salinan dalam sinkronisasi.
Ada beberapa pendekatan alternatif untuk ini, salah satu solusi populer untuk rendering multi-threaded adalah dengan menggunakan buffer-ganda perintah. Ini terdiri dari menjalankan back-end renderer di utas terpisah, di mana semua panggilan dan komunikasi dengan rendering API dilakukan. Thread front-end yang menjalankan logika game berkomunikasi dengan renderer back-end melalui buffer perintah (buffer ganda). Dengan pengaturan ini, Anda hanya memiliki satu titik sinkronisasi pada penyelesaian bingkai. Sementara front-end mengisi satu buffer dengan perintah render, back-end mengkonsumsi yang lain. Jika kedua utas seimbang, tidak ada yang harus kelaparan. Namun pendekatan ini bersifat suboptimal, karena memperkenalkan latensi pada frame yang diberikan, ditambah, driver OpenGL kemungkinan sudah melakukan ini dalam prosesnya sendiri, sehingga peningkatan kinerja harus diukur dengan cermat. Itu juga hanya menggunakan dua core, paling-paling. Pendekatan ini telah digunakan dalam beberapa game yang sukses, seperti Doom 3 dan Quake 3
Pendekatan yang lebih scalable yang membuat penggunaan multi-core CPU lebih baik adalah yang didasarkan pada tugas-tugas independen , di mana Anda menjalankan permintaan asinkron yang dilayani di utas sekunder, sedangkan utas yang memecat permintaan berlanjut dengan beberapa pekerjaan lain. Tugas idealnya tidak memiliki dependensi dengan utas lainnya, untuk menghindari kunci (juga menghindari data bersama / global seperti wabah!). Arsitektur berbasis tugas lebih dapat digunakan di bagian permainan yang dilokalkan, seperti animasi komputasi, penelusuran path AI, pembuatan prosedural, pemuatan dinamis alat peraga adegan, dll. Game secara alami penuh peristiwa, sebagian besar jenis peristiwa bersifat asink, mudah untuk membuatnya berjalan di utas terpisah.
Akhirnya, saya sarankan membaca:
Dasar-dasar Threading untuk Game oleh Intel.
Concurrency Efektif oleh Herb Sutter (beberapa tautan ke sumber daya bagus lainnya di halaman ini).
sumber