Alıntı (İbrahim):
> Mutex ve semaphore'un nerelerde kullanıldığına ya da ne için ihtiyaç olduğuna dair biraz daha bilgi verebilir misiniz?
Mutex: "Eğer bu kod bölümü bir iş parçacığı tarafından işletiliyorsa, onun işi bitene kadar başka iş parçacığı burayı işletmesin."
Semaphore: "Bu kod parçasını en fazla şu kadar sayıda iş parçacığı işletsin."
Alıntı:
> Anladığım kadarıyla mutex ve semaphore da thread'ler ile ilgili.
Programcının thread yönetimi için yararlandığı işletim sistemi olanakları...
Alıntı:
> Misal Thread eşzamanlı kod çalıştırmak için kullanılıyor
Evet, işletim sisteminin işlettiği her şey thread üzerinde işliyor: benim programım, Firefox, Emacs, köşedeki saat, vs. Her program tek thread üzerinde başlıyor. İşin güzeli, programlar kendileri de thread başlatabiliyorlar.
İşletim sistemi bunları art arda zaman aralıklarıyla işletiyor. Veya, günümüzdeki hemen hemen her bilgisayarda olduğu gibi birden fazla çekirdek varsa, birden fazla thread gerçekten de aynı anda işleyebiliyorlar.
Alıntı:
> fakat mutex ve semaphore tam olarak thread'in yapamadığı neleri yapıyor?
Onlar thread ile karşılaştırılacak kavramlar değil, thread yönetimi için kullanılan araçlar.
Alıntı:
> mutex de iş parçağını bekletiyor. Peki buna neden gerek duyuyoruz?
Bunu anlamak için mutex kullanılmadığında ne olduğunu yaşamak gerek. :)
Normalde std.parallelism ve std.concurrency kullanacak olsam da burada alt düzey olanaklardan yararlanalım. Kitaptaki "Basit bir bağlı liste" başlığı altındaki bağlı liste örneğini uyarlayalım:
http://ddili.org/ders/d/gostergeler.html
import std.stdio;
import std.conv;
import std.string;
import core.thread;
// Kitaptan farklı olarak 'shared' yaptım
shared struct Düğüm {
int eleman;
Düğüm * sonraki;
string toString() const {
string sonuç = to!string(eleman);
if (sonraki) {
sonuç ~= " -> " ~ to!string(*sonraki);
}
return sonuç;
}
// Kitaptan farklı olarak 'length' ekledim
size_t length() const {
return 1 + (sonraki ? sonraki.length : 0);
}
}
// Kitaptan farklı olarak 'shared' yaptım
shared struct Liste {
Düğüm * baş;
void başınaEkle(int eleman) {
baş = new shared(Düğüm)(eleman, baş);
}
string toString() const {
return format("(%s)", baş ? to!string(*baş) : "");
}
// Kitaptan farklı olarak 'length' ekledim
size_t length() const {
return baş ? baş.length : 0;
}
}
enum işParçacığıAdedi = 10;
enum elemanAdedi = 10; // Her thread bu kadar adet eleman ekleyecek
// Thread'lerin paylaştıkları bir liste
shared Liste sayılar;
// Her thread bu işlevi işletecek
void ekle(int threadNumarası) {
// Her thread farklı sayılar ekleyecek
const baş = threadNumarası * elemanAdedi;
const son = baş + elemanAdedi;
foreach (sayı; baş .. son) {
sayılar.başınaEkle(sayı);
Thread.sleep(10.msecs);
}
}
void main() {
Thread[] işçiler;
// İş parçacıkları oluşturuyoruz (henüz beklemedeler)
foreach (threadNumarası; 0 .. işParçacığıAdedi) {
/* Bu işleve neden gerek olduğu çok farklı bir konu: Bunu
* kullanmasak, bütün iş parçacıkları bu foreach
* değişkeninin son değerine sahip olan aynı
* threadNumarası'na sahip olurlar.
*/
auto işçiYap(int threadNumarası) {
return new Thread(() => ekle(threadNumarası));
}
işçiler ~= işçiYap(threadNumarası);
}
// İş parçacıklarını başlatıyoruz
foreach (işçi; işçiler) {
işçi.start();
}
// İşlerini bitirmelerini bekliyoruz
thread_joinAll();
writefln("%s adet eleman olmalı", işParçacığıAdedi * elemanAdedi);
writefln("%s adet eleman var: %s", sayılar.length, sayılar);
}
Bütün iş parçacıkları ekle() işlevini işletiyorlar. Büyük bir hatamız var çünkü birden fazla iş parçacığının düğüm eklemesine izin veriyoruz. Sonuçta, 100 adet eleman olmasını beklediğimiz halde rasgele sayıda eleman görüyoruz:
'
100 adet eleman olmalı
91 adet eleman var: (39 -> 99 -> 69 -> ... -> 0 -> 50 -> 20)
'
İş parçacıkları 'baş' göstergelerini okuyup yazarken birbirlerinin işlerini bozuyorlar.
Çözüm, listeye eleman ekleme gibi işlemleri mutex ile korumak; yani, o kodları birden fazla iş parçacığının işletmesine engel olmak. Programda yaptığım değişiklikler şunlar:
// Mutex için gerekli:
import core.sync.mutex;
// ...
// Listeyi ve mutex'i işçilere parametre olarak geçirdim:
void ekle(int threadNumarası, ref shared(Liste) sayılar, Mutex mutex) {
// Her thread farklı sayılar ekleyecek
const baş = threadNumarası * elemanAdedi;
const son = baş + elemanAdedi;
foreach (sayı; baş .. son) {
{
// Bir iş parçacığı bu kod bloğunu bitirmeden başka
// iş parçacığı bu kilidi ele geçiremeyecek.
mutex.lock();
// Buradan çıkıldığında mutex'i geri vereceğiz.
scope(exit) mutex.unlock();
sayılar.başınaEkle(sayı);
}
Thread.sleep(10.msecs);
}
}
// ...
void main() {
// Listeyi buraya aldım
shared sayılar = Liste();
auto mutex = new Mutex(); // Bunu da parametre olarak geçireceğiz:
// ...
// Kullanacakları liste ve mutex parametre olarak veriliyor:
return new Thread(() => ekle(threadNumarası, sayılar, mutex));
// ...
}
Artık beklendiği gibi hep 100 adet eleman var:
'
100 adet eleman olmalı
100 adet eleman var: (69 -> 59 -> 89 -> ... -> 80 -> 10 -> 0)
'
Sonuç: Mutex kullanılmadığında gösterge gibi değişkenler birden fazla iş parçacığı tarafından değiştirildiğinde birbirlerinin işlerini bozuyorlar. Kullanınca birisi işini bitirene kadar bekliyorlar ve işlemler doğru oluyor.
Bunları anlamak için deneyip görmekten daha iyi yöntem bilmiyorum. :)
Ali
--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]