Thread overview
Thread ve Multithreading Kavramları Hakkında Bilgi
Jan 16, 2017
İbrahim
Jan 17, 2017
İbrahim
January 16, 2017

Selamün Aleyküm Arkadaşlar;

İşletim sistemleri mimarisinde bildiğimiz üzere thread ve multithreading kavramları var. Thread'in aslında ne olduğunu bilsem de multithreading'i tam olarak bilmiyorum. Acaba bu ikisi tam olarak nedir ve farkları nelerdir?

Ayrıca Mutex ve Semaphore tam olarak nedir? Farklı nelerdir? Ayrıca hangisini hangi durumlarda kullanmak avantaj ve dezavantaj sağlar? Teşekkürler!

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

January 16, 2017

Alıntı (İbrahim):

>

Thread'in aslında ne olduğunu bilsem de

Ben de kısaca şöyle tanımlamışım:

http://ddili.org/ders/d/kosut_islemler.html#ix_kosut_islemler.i%C5%9F%20par%C3%A7ac%C4%B1%C4%9F%C4%B1
http://ddili.org/ders/d/es_zamanli.html#ix_es_zamanli.i%C5%9F%20par%C3%A7ac%C4%B1%C4%9F%C4%B1

Alıntı:

>

multithreading'i tam olarak bilmiyorum. Acaba bu ikisi tam olarak nedir ve farkları nelerdir?

"multi" ve "thread" sözcüklerinden türetilmiş bir fiil; bir programın birden fazla iş parçacığı kullanması anlamına geliyor.

Alıntı:

>

Ayrıca Mutex ve Semaphore tam olarak nedir?

Mutex, kapıyı açan tek anahtar bulunan durumlarda kullanılıyor. Mutex yalnızca tek iş parçacığının (thread) elinde bulunabiliyor. Aynı mutex'i eline geçirmek isteyen başka iş parçacığı varsa, onlar mutex'in sahibinin mutex'i bırakmasını bekliyorlar. Hepsi şu mantığı işletiyorlar:
'

  1. Mutex'i al
  2. İşini yap
  3. Mutex'i bırak
    '
    İşletim sistemi 1 numaralı adımı işletirken mutex sahipsizse veriyor, değilse bu adımın işletmeye çalışan iş parçacığını bekletiyor.

Mutex, işini yap adımı sırasında kullanılan bir kaynağın doğru çalışması için kullanılıyor. Örneğin, bütün iş parçacıkları aynı müşteri listesini kullanıyorlardır ve işini yap sırasında o listeye bir müşteri ekleniyordur. Müşteri listesine ekleme işlemi bir kaç alt adımdan oluşuyorsa, yani atomik değilse, belirli bir anda tek iş parçacığının işlemesini sağlamak için mutex'ten yararlanılıyor.

Semaphore, gemicilikte uzaktan haberleşmek için kullanılanlar gibi bayrak anlamına geliyor. Ben ilk (ve tek) karşılaştığımda bir kaynaktan kaç tane kullanıldığını yönetmek için kullanmıştık. Semaphore 10 gibi bir sayı tutuyor. İş parçacıkları semaphore'u ele geçirmek istediklerinde işletim sistemi o sayı sıfır olana kadar izin veriyor ve sayıyı bir azaltıyor. Dolayısıyla, örneğin bir seferde 10 adet iş parçacığı işleyebiliyor. Örneğin, veri tabanımız yavaştır ve bir seferde en fazla 10 sorgu işletmek istiyoruzdur...

Semaphore'un değeri 1 olduğunda bir iş parçacığının diğerine ben işimi bitirdim, sıra sende demesi için de kullanılabilir. Birincisi eline geçirdiğinde değer 0 olur, işini bitirdiğinde 1 olur ve diğeri işine devam edebilir.

Mutex ve semaphore'dan başka araçlar da var ama neyse ki hataya çok açık olan multithreading konusunun bu kadar alt düzey olanaklarını kullanmak zorunda kalmıyoruz. D'de std.parallelism, std.concurrency, ve std.thread.Fiber var. :)

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

January 17, 2017

Yanıtınız için teşekkürler Ali Hocam;

Mutex ve semaphore'un nerelerde kullanıldığına ya da ne için ihtiyaç olduğuna dair biraz daha bilgi verebilir misiniz? Anladığım kadarıyla mutex ve semaphore da thread'ler ile ilgili. Misal Thread eşzamanlı kod çalıştırmak için kullanılıyor fakat mutex ve semaphore tam olarak thread'in yapamadığı neleri yapıyor? Ya da daha doğrusu:
Alıntı:

>

İşletim sistemi 1 numaralı adımı işletirken mutex sahipsizse veriyor, değilse bu adımın işletmeye çalışan iş parçacığını bekletiyor.

iş parçacığı (thread) aslında belli kodları eşzamanlı çalıştırıyor. Lakin mutex de iş parçağını bekletiyor. Peki buna neden gerek duyuyoruz? Çünkü bu bekletme, kodlarda zaten otomatik oluyor, bunu eşzamanlı yapmak için zaten thread kullanıyoruz. Mutex ve semaphore'u tam olarak anlayamadım.

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

January 17, 2017

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