Saya membuat mesin permainan 2D sederhana dan saya ingin memperbarui dan membuat sprite di utas yang berbeda, untuk mempelajari bagaimana hal itu dilakukan.
Saya perlu menyinkronkan utas pembaruan dan render. Saat ini, saya menggunakan dua bendera atom. Alur kerjanya terlihat seperti:
Thread 1 -------------------------- Thread 2
Update obj ------------------------ wait for swap
Create queue ---------------------- render the queue
Wait for render ------------------- notify render done
Swap render queues ---------------- notify swap done
Dalam pengaturan ini saya membatasi FPS utas render ke FPS utas pembaruan. Selain itu saya gunakan sleep()
untuk membatasi render dan memperbarui FPS utas hingga 60, jadi dua fungsi menunggu tidak akan menunggu banyak waktu.
Masalahnya adalah:
Penggunaan CPU rata-rata sekitar 0,1%. Kadang-kadang naik hingga 25% (dalam quad core PC). Ini berarti bahwa utas sedang menunggu yang lain karena fungsi tunggu adalah loop sementara dengan fungsi tes dan set, dan loop sementara akan menggunakan semua sumber daya CPU Anda.
Pertanyaan pertama saya adalah: apakah ada cara lain untuk menyinkronkan kedua utas? Saya perhatikan bahwa std::mutex::lock
jangan gunakan CPU saat sedang menunggu untuk mengunci sumber daya sehingga tidak loop sementara. Bagaimana cara kerjanya? Saya tidak dapat menggunakan std::mutex
karena saya harus mengunci mereka di satu utas dan membuka kunci di utas lainnya.
Pertanyaan lainnya adalah; karena program selalu berjalan pada 60 FPS mengapa terkadang penggunaan CPU-nya melonjak hingga 25%, artinya salah satu dari keduanya menunggu banyak? (kedua utas keduanya terbatas pada 60fps sehingga idealnya tidak perlu banyak sinkronisasi).
Sunting: Terima kasih atas semua balasan. Pertama saya ingin mengatakan saya tidak memulai utas baru setiap frame untuk di-render. Saya memulai pembaruan dan render loop di awal. Saya pikir multithreading dapat menghemat waktu: Saya memiliki fungsi-fungsi berikut: FastAlg () dan Alg (). Alg () adalah kedua saya Perbarui obj dan render obj dan Fastalg () adalah "kirim antrian render ke" renderer "". Dalam satu utas:
Alg() //update
FastAgl()
Alg() //render
Dalam dua utas:
Alg() //update while Alg() //render last frame
FastAlg()
Jadi mungkin multithreading dapat menghemat waktu yang sama. (sebenarnya dalam aplikasi matematika sederhana yang dilakukannya, di mana alg adalah algoritma yang panjang dan yang lebih cepat)
Saya tahu bahwa tidur bukanlah ide yang baik, meskipun saya tidak pernah memiliki masalah. Apakah ini akan lebih baik?
While(true)
{
If(timer.gettimefromlastcall() >= 1/fps)
Do_update()
}
Tapi ini akan menjadi loop sementara yang akan menggunakan semua CPU. Bisakah saya menggunakan sleep (angka <15) untuk membatasi penggunaan? Dengan cara ini ia akan berjalan pada, misalnya, 100 fps, dan fungsi pembaruan akan dipanggil hanya 60 kali per detik.
Untuk menyinkronkan dua utas, saya akan menggunakan waitforsingleobject dengan createSemaphore jadi saya akan dapat mengunci dan membuka kunci di utas yang berbeda (tidak menggunakan perulangan sementara), bukan?
sumber
Jawaban:
Untuk mesin 2D sederhana dengan sprite, pendekatan single-threaded sangat baik. Tetapi karena Anda ingin belajar bagaimana melakukan multithreading, Anda harus belajar melakukannya dengan benar.
Tidak
sleep
untuk mengontrol laju bingkai. Tidak pernah. Jika seseorang memberi tahu Anda, pukul mereka.Pertama, tidak semua monitor bekerja pada 60Hz. Kedua, dua timer yang berdetak pada laju yang sama berjalan berdampingan akhirnya akan selalu tidak sinkron (letakkan dua bola pingpong di atas meja dari ketinggian yang sama, dan dengarkan). Ketiga,
sleep
secara desain tidak akurat atau dapat diandalkan. Granularity mungkin sama buruknya dengan 15.6ms (pada kenyataannya, default pada Windows [1] ), dan sebuah frame hanya 16.6ms pada 60fps, yang menyisakan 1ms hanya untuk yang lainnya. Plus, sulit untuk mendapatkan 16,6 menjadi kelipatan 15,6 ...Juga,
sleep
diizinkan untuk (dan kadang-kadang akan!) Kembali hanya setelah 30 atau 50 atau 100 ms, atau waktu yang lebih lama.std::mutex
untuk memberi tahu thread lain. Ini bukan untuk apa.Melakukan
sleep
[2] . Juga, pengatur waktu berulang menghitung waktu dengan benar (termasuk waktu yang lewat di antaranya) sedangkan tidur selama 16,6 ms (atau 16,6 ms minus diukur_time_elapsed) tidak.std::mutex
untuk hanya memiliki satu utas mengakses sumber pada suatu waktu ("saling mengecualikan"), dan untuk mematuhi semantik anehstd::condition_variable
.std::condition_variable
untuk memblokir utas lainnya sampai beberapa kondisi benar. Semantikstd::condition_variable
dengan mutex tambahan itu diakui cukup aneh dan bengkok (kebanyakan karena alasan historis yang diwarisi dari utas POSIX), tetapi variabel kondisi adalah primitif yang tepat untuk digunakan untuk apa yang Anda inginkan.Jika Anda merasa
std::condition_variable
terlalu aneh untuk merasa nyaman dengan itu, Anda juga bisa menggunakan acara Windows (sedikit lebih lambat), atau, jika Anda berani, bangun acara sederhana Anda sendiri di sekitar NtKeyedEvents (melibatkan hal-hal menakutkan tingkat rendah). Saat Anda menggunakan DirectX, Anda sudah terikat dengan Windows, jadi kehilangan portabilitas seharusnya tidak menjadi masalah besar.[1] Ya, Anda dapat mengatur kecepatan penjadwal menjadi 1ms, tetapi ini disukai karena menyebabkan lebih banyak konteks beralih, dan mengkonsumsi lebih banyak daya (di dunia di mana semakin banyak perangkat adalah perangkat seluler). Ini juga bukan solusi karena masih tidak membuat tidur lebih dapat diandalkan.
[2] Pengatur waktu akan meningkatkan prioritas utas, yang memungkinkannya menghentikan utas menengah-prioritas lain yang setara dan dijadwalkan terlebih dahulu, yang merupakan perilaku kuasi-RT. Ini tentu saja bukan RT yang benar, tetapi sangat dekat. Bangun dari tidur hanya berarti bahwa utas menjadi siap dijadwalkan pada suatu waktu, kapan pun itu mungkin.
sumber
Saya tidak yakin apa yang ingin Anda capai dengan membatasi FPS dari Pembaruan dan Membuat keduanya menjadi 60. Jika Anda membatasi mereka pada nilai yang sama, Anda bisa meletakkannya di utas yang sama.
Tujuan ketika memisahkan Pembaruan dan Render dalam utas yang berbeda adalah untuk memiliki "hampir" yang independen satu sama lain, sehingga GPU dapat membuat 500 FPS dan logika Pembaruan masih berjalan pada 60 FPS. Anda tidak mencapai perolehan kinerja yang sangat tinggi dengan melakukannya.
Tapi kamu bilang kamu hanya ingin tahu cara kerjanya, dan tidak apa-apa. Dalam C ++, mutex adalah objek khusus yang digunakan untuk mengunci akses ke sumber daya tertentu untuk utas lainnya. Dengan kata lain, Anda menggunakan mutex untuk membuat data yang masuk akal dapat diakses hanya dengan satu utas pada saat itu. Untuk melakukannya, cukup sederhana:
Sumber: http://en.cppreference.com/w/cpp/thread/mutex
EDIT : Pastikan mutex Anda adalah kelas atau file-lebar, seperti pada tautan yang diberikan, atau setiap thread akan membuat mutex sendiri dan Anda tidak akan mencapai apa pun.
Utas pertama untuk mengunci mutex akan memiliki akses ke kode di dalamnya. Jika utas kedua mencoba memanggil fungsi kunci (), itu akan diblok sampai utas pertama membuka kunci. Jadi mutex adalah fungsi blok, tidak seperti loop sementara. Fungsi pemblokiran tidak akan memberi tekanan pada CPU.
sumber
std::lock_guard
atau serupa, bukan.lock()
/.unlock()
. RAII bukan hanya untuk manajemen memori!