Çoklu okuma: Yanlış mı yapıyorum?


23

Müzik çalan bir uygulama üzerinde çalışıyorum.

Oynatma sırasında, genellikle aynı konu üzerinde olmaları gerektiğinden, ayrı işlerde her şey olması gerekir. Çağrı:. Örneğin, her biri içinde çalınacak kendi iş parçacığı atanır, böylece bir akor ihtiyacının notları (Düzen netleştirmek için, birlikte duyulmak note.play()not bitti iskambil kadar iplik donar ve ben üç ihtiyacım bu yüzden aynı anda üç nota duymak için ayrı başlıklar.)

Bu tür bir davranış, bir müzik parçasının çalınması sırasında birçok konu yaratır.

Örneğin, kısa bir melodi ve eşlik eden kısa akor ilerlemeli bir müzik parçası düşünün. Tüm melodi, tek bir iplik üzerinde çalınabilir, fakat akorlarının her birinde üç nota bulunduğundan, ilerlemenin çalması için üç iplik gerekir.

Dolayısıyla, bir ilerlemenin oynatılması için sözde kod şöyle görünür:

void playProgression(Progression prog){
    for(Chord chord : prog)
        for(Note note : chord)
            runOnNewThread( func(){ note.play(); } );
}

Dolayısıyla, ilerlemenin 4 akoru olduğunu varsayarız ve onu açtıktan 3 notes * 4 chords * 2 times= 24 dişe göre iki defa çalarız. Ve bu sadece bir kez oynamak için.

Aslında pratikte iyi çalışıyor. Belirgin bir gecikme veya bundan kaynaklanan hatalar fark etmiyorum.

Fakat bunun doğru bir uygulama olup olmadığını veya temelde yanlış bir şey yapıp yapmadığımı sormak istedim. Kullanıcı bir düğmeye her basışında çok fazla iş parçacığı oluşturmak mantıklı mıdır? Değilse, nasıl farklı yapabilirim?


14
Belki de bunun yerine sesi karıştırmayı düşünmelisiniz? Hangi çerçeveyi kullandığınızı bilmiyorum ama işte bir örnek: wiki.libsdl.org/SDL_MixAudioFormat Veya şu kanalları kullanabilirsiniz: libsdl.org/projects/SDL_mixer/docs/SDL_mixer_25.html#SEC25
Rufflewind

5
Is it reasonable to create so many threads...dilin iş parçacığı modeline bağlıdır. Paralellik için kullanılan başlıklar genellikle işletim sistemi düzeyinde ele alınmakta, böylece işletim sistemi bunları birden fazla çekirdeğe eşleyebilmektedir. Bu dişlerin oluşturulması ve arasında geçiş yapılması pahalıdır. Eşzamanlılık için konular (iki görevi bir araya getirmek, her ikisini de aynı anda uygulamak zorunda değilsiniz) dilde / VM düzeyinde uygulanabilir ve üretmek ve arasında geçiş yapmak için 10 ağ soketi ile az ya da çok konuşabilirsiniz. Aynı anda, ancak mutlaka bu şekilde daha fazla CPU verimi almazsınız.
Doval

3
Bu bir yana, diğerleri bir kerede birden fazla ses çalmak için yanlış bir yol olduğu konusunda kesinlikle haklıdır.
Doval

3
Ses dalgalarının nasıl çalıştığı hakkında çok bilginiz var mı? Genellikle, iki ses dalgasının değerlerini (aynı bit hızında belirtilen) birlikte yeni bir ses dalgasına dönüştürerek bir akor yaparsınız. Karmaşık dalgalar basit olanlardan oluşturulabilir; Bir şarkıyı çalmak için sadece tek bir dalga biçimine ihtiyacınız olacak.
KChaloux

Note.play () 'in asenkron olmadığı söylendiğinden, her note.play () için bir iş parçacığı aynı anda birden fazla nota çalmak için bir yaklaşımdır. UNLESS .., bu notları tek bir iş parçacığında oynayacağınız şekilde birleştirebilirsiniz. Bu mümkün değilse, yaklaşımınızla senkronize kaldıklarından emin olmak için bazı mekanizmalar kullanmak zorunda kalacaksınız
pnizzle

Yanıtlar:


46

Yaptığınız bir varsayım geçerli olmayabilir: (diğer şeylerin yanı sıra) iş parçacığınızın eşzamanlı olarak çalıştırılmasını gerektirir. 3 için çalışabilir, ancak bir noktada sistemin ilk önce hangi konuların çalışacağını ve hangisinin bekleyeceğini önceliklendirmesi gerekecektir.

Uygulamanız sonuçta API'nize bağlı olacaktır, ancak modern API'lerin çoğu, ne zaman oynamak istediğinizi söylemenize ve zamanlamayı ve sıraya koyma işlemlerini kendinize sıraya koymanızı sağlar. Eğer böyle bir API'yi kendiniz kodlayacak olsaydınız, mevcut herhangi bir sistem API'sini (neden olmasın ?!) göz ardı ederek, notlarınızı karıştırıp tek bir iş parçacığından çalmak bir olay kuyruğu, not modeli başına bir iş parçacığından daha iyi bir yaklaşım gibi görünüyor.


36
Veya farklı şekilde ifade etmek için - sistem başladıktan sonra herhangi bir dişin sırasını, sırasını veya süresini garanti etmeyecek ve garanti edemez.
James Anderson

2
@JamesAnderson, eşitleme konusunda büyük çabalar sarf etmesi ve nihayetinde yine neredeyse kasten olmasından kaynaklanacak olan hariç.
Mark

'API' ile kullandığım ses kütüphanesini mi kastediyorsunuz?
Aviv Cohn,

1
@Prog Evet. Note.play ()
ptyx

26

Lockstep'te yürütülen iş parçacıklarına güvenmeyin. Tanıdığım her işletim sistemi, ipliklerin zaman içinde birbirleriyle tutarlı şekilde çalışacağını garanti etmez. Bunun anlamı, eğer CPU 10 iş parçacığı çalıştırırsa, belirli bir saniyede mutlaka eşit zaman alamazlar. Hızlı bir şekilde senkronizasyondan çıkabilirler veya mükemmel bir şekilde senkronizasyonda kalabilirler. Bu, iş parçacığının doğasıdır: hiçbir şey garanti edilmez, çünkü onların yürütülmesi davranışları belirleyici değildir .

Bu özel uygulama için, müzik notaları tüketen tek bir konuya sahip olmanız gerektiğine inanıyorum . Notları tüketmeden önce, başka bazı işlemlerin, enstrümanları, çalışanları, ne olursa olsun, tek bir müzik parçasında birleştirmesi gerekir .

Örneğin not üreten üç iş parçacığı olduğunu varsayalım. Onları, hepsinin kendi müziğini koyabilecekleri tek bir veri yapısı üzerinde senkronize ederdim. Bir başka iş parçacığı (tüketici) birleştirilmiş notları okur ve oynatır. İplik zamanlamasının notların kaybolmamasına neden olmadığından emin olmak için burada kısa bir gecikme olması gerekebilir.

İlgili okuma: üretici-tüketici sorunu .


1
Cevapladığınız için teşekkürler. Cevapınız olduğunu anladığımdan% 100 emin değilim, ancak aynı anda 3 nota çalmak için neden 3 parçacığa ihtiyacım olduğunu anladığınızdan emin olmak istedim: bunun nedeni, çağrının note.play()notun çalınmasına kadar parçayı dondurmasıdır. Bu yüzden play()aynı anda 3 nota yazabilmem için , bunu yapmak için 3 farklı konuya ihtiyacım var. Çözümün benim durumumu ele alıyor mu?
Aviv Cohn,

Hayır, ve bu sorudan net değildi. Bir akorun akor çalması mümkün değil mi?

4

Bu müzik problemi için klasik bir yaklaşım tam olarak iki konu kullanmak olacaktır . Birincisi, daha düşük öncelikli bir iş parçacığı kullanıcı arayüzü veya ses üreten kod gibi

void playProgression(Progression prog){
    for(Chord chord : prog)
        for(Note note : chord)
            otherthread.startPlaying(note);
}

(notu yalnızca eşzamansız olarak başlatma ve bitmesini beklemeden devam etme kavramına dikkat edin)

ve ikincisi, gerçek zamanlı iş parçacığı , şu anda çalması gereken tüm notalara, seslere, örneklere vs. sürekli olarak bakardı; onları karıştırın ve son dalga formunu çıkartın. Bu kısım, cilalı bir üçüncü taraf kütüphanesinden alınabilir (ve çoğu zaman).

Bu iş parçacığı genellikle kaynakların "açlığa" çok duyarlıdır - herhangi bir gerçek ses çıkışı işlemi ses kartı tamponlu çıktınızdan daha uzun bir süre boyunca engellenirse, kesintiler veya patlamalar gibi duyulabilir eserler ortaya çıkar. Doğrudan ses çıkaran 24 iş parçacığınız varsa, o zaman birinin bir noktada kekeme olasılığı çok daha yüksektir. Bu genellikle kabul edilemez olarak kabul edilir, çünkü insanlar ses bozukluklarına (görsel eserlerden çok daha fazla) duyarlıdır ve küçük kekemeliklerin bile farkına varırlar.


1
OP, API’nin aynı anda yalnızca bir nota çalabildiğini söylüyor
Mooing Duck

4
@MooingDuck API karıştırıp karıştırmazsa karıştırmalıdır; OP, API'nin karışamayacağını söylüyorsa çözüm kodunuzda karıştırmak ve diğer iş parçacığının api üzerinden my_mixed_notes_from_whole_chord_progression.play () işlevini gerçekleştirmesini sağlamaktır.
Peteris

1

Evet, yanlış bir şey yapıyorsun.

İlk şey, Threads yaratmanın pahalı olmasıdır. Bir işlevi çağırmaktan çok daha fazla ek yükü var.

Öyleyse yapmanız gereken, eğer bu iş için birden fazla iş parçacığına ihtiyacınız varsa, iş parçacıklarını geri dönüştürmektir.

Bana göründüğü gibi, diğer konuların programlanmasını yapan bir ana konu var. Böylece ana müzik çalınmakta ve çalınacak yeni bir nota olduğunda yeni başlıklar başlamaktadır. Bu yüzden, dişlilerin ölmesine ve yeniden başlatılmasına izin vermek yerine, çalınacak yeni bir nota olup olmadığını ya da başka bir şekilde uyuyacaksa, her bir x milisaniyeyi (ya da nanosaniye) kontrol ettikleri diğer iplikleri bir uyku döngüsünde canlı tutmalısınız. Ana iş parçacığı daha sonra yeni iş parçacığı başlatmaz, ancak varolan iş parçacığı havuzuna nota çalmasını söyler. Yalnızca havuzda yeterince iş parçacığı yoksa yeni iş parçacığı oluşturabilir.

Bir sonraki senkronizasyon. Neredeyse herhangi bir modern okuyuculu sistemler aslında tüm dişlerin aynı anda yapılmasını garanti etmez. Makinede çalışan çekirdeklerden daha fazla iş parçacığınız ve işlemleriniz varsa (bu genellikle durum böyledir), iş parçacığı CPU zamanının% 100'ünü alamaz. İşlemciyi paylaşmak zorundalar. Bu, her iş parçacığının küçük miktarda CPU zamanı alması ve daha sonra paylaşımın ardından bir sonraki iş parçacığının kısa bir süre için CPU alması anlamına gelir. Sistem, iş parçacığınızın diğer iş parçacıklarıyla aynı CPU zamanına sahip olmasını garanti etmez. Bu, bir dişlinin bir başkasının bitmesini beklediği ve bu nedenle geciktiği anlamına gelir.

Bir iş parçacığında birden fazla not çalmanın mümkün olup olmadığına bir göz atmayı tercih edin, böylece iş parçacığı tüm notları hazırlar ve ardından sadece "start" komutunu verir.

Konu ile yapmanız gerekiyorsa, en azından ipleri tekrar kullanın. Öyleyse, tüm parçadaki notalarda olduğu kadar çok iş parçacığına sahip olmanız gerekmez, ancak yalnızca aynı anda oynanan maksimum nota sayısı kadar çok iş parçasına ihtiyacınız olur.


Msgstr "[[]] tek bir iş parçacığında birden fazla not çalmak mümkün mü, böylece iş parçacığı tüm notları hazırlar ve sonra sadece" start "komutunu verir. - Bu benim de ilk düşüncemdi. Bazen, çok iş parçacığı konusunda yorum yapmaktan korktuğumuzu merak ediyorum (örneğin, programmers.stackexchange.com/questions/43321/… ), tasarım sırasında çok fazla programcının yoldan gitmesine neden olmaz. Ben bir sürü iş parçacığına ihtiyaç duyduğumda ortaya çıkacak büyük, açık bir işlemden şüpheliyim. Tek iş parçacıklı bir çözüme dikkatle bakmanızı tavsiye ederim.
user1172763

1

Pragmatik cevap, başvurunuz için işe yararsa ve mevcut gereksinimlerinizi karşılıyorsa, o zaman yanlış yapmazsınız :) Ancak , "bu ölçeklenebilir, verimli, yeniden kullanılabilir bir çözüm" anlamına geliyorsa, cevabınız hayırdır. Tasarım, farklı durumlarda (daha uzun melodiler, daha fazla eşzamanlı nota, farklı donanım vb.) Gerçek olabileceği veya olamayabileceği konuların nasıl davranacağına dair birçok varsayımda bulunur.

Alternatif olarak bir zamanlama döngüsü ve iş parçacığı havuzu kullanmayı düşünün . Zamanlama döngüsü kendi iş parçacığında çalışır ve bir notanın çalınması gerekip gerekmediğini sürekli olarak kontrol eder. Bunu, sistem zamanını melodinin başlatıldığı zamanla karşılaştırarak yapar. Her notanın zamanlaması normal olarak melodi temposundan ve notalardan kolayca hesaplanabilir. Yeni bir notun çalınması gerektiğinden, bir iplik havuzundan bir iplik ödünç play()alın ve nottaki işlevi çağırın . Zamanlama döngüsü çeşitli şekillerde çalışabilir, ancak en basit olanı CPU kullanımını en aza indirgemek için kısa uykularla (muhtemelen akorlar arasında) sürekli bir döngüdür.

Bu tasarımın bir avantajı, iplik sayısının maksimum eşzamanlı nota + 1 (zamanlama döngüsü) sayısının aşılmamasıdır. Ayrıca zamanlama döngüsü sizi iplik gecikmesinin neden olabileceği zamanlamalarda kaymalara karşı korur. Üçüncüsü, melodi temposu sabit değildir ve zamanlamaların hesaplanması değiştirilerek değiştirilebilir.

Diğer taraftan, işlevin note.play()çalışmak için çok zayıf bir API olduğu diğer yorumcularla aynı fikirdeyim . Herhangi bir makul ses API'si notları çok daha esnek bir şekilde karıştırmanıza ve zamanlamanıza izin verir. Bu, bazen sahip olduklarımızla yaşamak zorunda olduğumuzu söyledi :)


0

Kullanmanız gereken API olduğunu farz ederek basit bir uygulama olarak bana iyi gözüküyor . Diğer cevaplar bunun neden çok iyi bir API olmadığını kapsar, bu yüzden bundan daha fazla bahsetmiyorum, bununla birlikte yaşamak zorunda olduğunuzu varsayıyorum. Yaklaşımınız çok sayıda iplik kullanacak, ancak konu sayısı düzinelerce olduğu sürece, endişelenmemesi gereken modern bir bilgisayarda.

Yapmanız gereken bir şey varsa (mümkünse (kullanıcı klavyeye basmak yerine dosyadan çalmak gibi), gecikme eklemek). Böylece bir iplik başlatırsınız, sistem saatinin belirli bir saatine kadar uyur ve doğru zamanda bir nota çalmaya başlar (not, bazen uyku erken kesilebilir, bu nedenle saati kontrol edin ve gerekirse daha fazla uyuyun). İşletim sisteminin tam olarak uykunuz bittiğinde (gerçek zamanlı bir işletim sistemi kullanmadığınız sürece) iş parçacığına devam edeceği garantisi olmasa da, sadece iş parçacığını başlatmak ve zamanlamayı kontrol etmeden oynamaya başlamaktan çok daha doğru olması muhtemeldir. .

Sonra işleri biraz zorlaştıran ama çok fazla olmayan ve yukarıda belirtilen gecikmeyi azaltmanıza izin verecek bir sonraki adım, bir iş parçacığı havuzu kullanmaktır. Yani, bir iş parçacığı bir notu bitirdiğinde, çıkmaz, ama bunun yerine yeni bir notun çalmasını bekler. Ve bir nota çalmaya başlamayı talep ettiğinizde, ilk önce havuzdan ücretsiz bir konu almaya çalışın ve sadece gerekirse yeni bir konu ekleyin. Bu, tabii ki, mevcut ateşle ve unut yaklaşımınız yerine, basit bir konu dizisi iletişimi gerektirecektir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.