Objective-C'de Thread Sinyalizasyonu ve Senkronizasyon Yöntemleri

Objective-C'de Thread Sinyalizasyonu ve Senkronizasyon Yöntemleri

Objective-C'de thread sinyalizasyonu ve senkronizasyon yöntemleri hakkında bilgi edinin Bu makalede, farklı senkronizasyon yöntemleri ve hangi senkronizasyon yönteminin hangi senaryolar için en uygun olduğu detaylı bir şekilde ele alınmaktadır Okumaya başlayın ve uygulamanızı daha da geliştirin!

Objective-C'de Thread Sinyalizasyonu ve Senkronizasyon Yöntemleri

Objective-C dilinde multi-threading, yani aynı anda birden fazla threadin çalışması oldukça yaygın olarak kullanılan bir yöntemdir. Ancak, birçok threadin aynı anda aynı veri kaynağını kullanması ya da işlem yapması sırasında bazı senkronizasyon yöntemleri kullanılmazsa, programların hatalı sonuçlar üretebilir. Bu sebeple Objective-C’de thread sinyalizasyonu ve senkronizasyon yöntemleri oldukça önemlidir.

Thread sinyalizasyonu ve senkronizasyon yöntemleri, birçok thread'in çalışmasını düzenler ve veri kaynaklarının paylaşımı sırasında hataların önüne geçer. NSLock, NSRecursiveLock ve NSConditionLock bu yöntemler arasında yer alır ve ortak bir veri yapısının kullanımını kontrol eder. Ayrıca Semaphore, birden çok thread'in aynı anda kaynaklar üzerinde çalışmasını denetler ve Dispatch Queue'lar, blok tabanlı multi-threading için kullanılır. Kilitli kuyruk yapıları ise threadler arası iletişim ve senkronizasyon için kullanılır.


Multithreading Nedir?

Çoklu görev işleme veya multithreading, bir programın işletim sistemi tarafından sağlanan kaynakların zamanlama planlamasına uygun olarak birçok işlem gerçekleştirmesi anlamına gelir. Bu sayede işlemler paralel olarak çalışabilir ve kullanıcının işletim sistemi ile olabildiğince etkileşimli olması sağlanır.

Multithreading, uygulama geliştiricilerinin işlemleri daha verimli, hızlı ve stabil hale getirmesine olanak tanır. Böylece çoklu iş parçacıkları kullanmak, performansı en üst düzeye çıkarır ve sistemi kullanım açısından daha iyi yönetmenize olanak sağlar.


Thread Senkronizasyonu Nedir?

Objective-C dilinde threadler arasında senkronizasyon sağlamak oldukça önemlidir. Threadlerin ortak kaynaklara erişimleri ve çalışma sıralarının belirlenmesi için uygun bir senkronizasyon yöntemi kullanmamız gerekiyor. Aksi takdirde, birden fazla thread aynı anda aynı işlemleri yapabilir ve buna bağlı olarak programımızın çalışması istenilen şekilde gerçekleşmeyebilir.

Senkronizasyon yöntemleri kullanarak, threadler arasında uygun bir iletişim kurabilir ve her bir threadin belirlenmiş bir zaman diliminde çalışmasını sağlayabiliriz. Lock (kilit) kullanımı, senkronizasyonda sıklıkla tercih edilen bir yöntemdir. Bir veri yapısına birden fazla thread’in erişimini kontrol etmek için NSLock, Nested tipinde işlemler yapılabilen NSRecursiveLock ve bir yada birkaç threadin geçiş kontrolünde kullanılan NSConditionLock kullanılabilir.

Birden fazla thread arasındaki kaynak paylaşımını kontrol altında tutmak ve senkronize etmek için de Semaphore kullanabiliriz. Bu senkronizasyon aracı, ortak bir kaynağın aynı anda yalnızca belli sayıda thread tarafından kullanılmasına izin verir. Dispatch Queue’lar, blok tabanlı multi-threading için kullanılır. Sıralı işlemleri gerçekleştirmek için Serial Queue, aynı anda birden fazla işlemi gerçekleştirmek için ise Concurrent Queue kullanılır.

Kilitli kuyruk yapıları, threadler arası iletişim ve senkronizasyonu sağlamak için oldukça etkilidir. Dispatch Group, tamamlanmış işlerin sayısını takip ederken, Dispatch Barrier barrier bloğuna kadar bütün işlemleri bitirir ve o zaman barrier bloğunun çalışmasına izin verir. Tüm bu yöntemler sayesinde Objective-C dilinde thread senkronizasyonu sağlamak oldukça kolay hale gelir.


Locklar

Threadler arasında ortak bir kaynağa erişim sağlarken, birden çok threadin aynı anda aynı işlemi yapmasına izin verilmez. Bu durumda, kilit (lock) kullanımı devreye giriyor. Locklar, birden çok threadin belirli bir işleme erişimini kontrol etmek için kullanılır. Objective-C'de, üç ana lock türü vardır ve bu locklar için Apple tarafından sağlanan sınıflar vardır.

NSLock, threadler arasındaki bir veri yapısının paylaşımını kontrol etmek için kullanılır. Yan yana iki NSLock varsa, sıradan bir lock olarak çalışır. Eğer birkaç lock bir arada kullanılırsa (bir NSLock içinde başka bir NSLock), o zaman seri bir lock mahiyeti taşır.

NSRecursiveLock, nested (iç içe) lock yapısı ile aynı işlemi birçok kez çalıştırabilen bir yapıdır. Yani bir thread bir kez bir NSRecursiveLock' u sıkmışsa, aynı thread birkaç kez daha bu NSRecursiveLock'u sıkabilir.

NSConditionLock, bir veya birkaç threadin geçiş kontrolünde kullanılan bir lock türüdür. Bu lock, belirli bir koşul yerine getirilene kadar bekletilir. Daha sonra o koşula uygun olan thread, diğer threadleri geçerek çalışmalarına devam edebilir.


NSLock

Objective-C’de thread senkronizasyonu ve sinyalizasyonu sağlamak için kullanılan birçok yöntem bulunmaktadır. Bunlardan biri de NSLock’tur. Bu yapının amacı, threadler arasında belirlenen paylaşılan bir veri yapısının kullanımına izin verirken, diğer threadlerin aynı anda bu veri yapısına erişmesini engellemektir. Bu sayede veri yapısı üzerindeki istenmeyen değişikliklerin önüne geçilebilir.

NSLock, işletim sistemi tarafından sağlanan bir kilit mekanizması kullanarak tüm işlemleri senkronize eder. Bu sayede, bir threadin bu veri yapısında bir değişiklik yapması ve diğer threadlerin bu veri yapısına erişmesi engellenir. Ancak, NSLock yapısının kullanımı zaman zaman diğer alternatiflere göre daha farklıdır. Örneğin, NSRecursiveLock yapısı nested (iç içe) lock yapısını kullanarak aynı thread tarafından birden fazla kez işleme sokulabilen bir yapı sağlar.

NSLock yapısı, kullanımı oldukça basit olan bir yapıdır. İlk olarak bir NSLock nesnesi oluşturulmalıdır. Daha sonra, geçerli thread bu kaynağı kullanmak istediğinde kilitleme işlemi gerçekleştirilir. İşlem tamamlandığında ise kilitleme işlemi kaldırılır. Her bir NSLock nesnesi yalnızca bir thread tarafından tutulabilir. Dolayısıyla, NSLock yapısının kullanımı oldukça güvenilir bir şekilde gerçekleştirilebilir.


NSRecursiveLock

Thread senkronizasyonunda kullanılan lock yapısının, en gelişmiş türüdür. İç içe kullanıma izin verir ve aynı thread tarafından birçok kez işleme sokulabilen bir yapı sağlar. İç içe kullanıma izin vermesi sayesinde, bir thread kendi kaynaklarını serbest bırakmadan diğer threadlerin de aynı kaynakları kullanmasına izin veriyor.

Metot Açıklama
lock Kilitleyerek, kaynağa erişimi engeller. Eğer kaynak başka bir thread tarafından kullanılıyorsa, kilitlenene kadar bekler.
unlock Kaynağı serbest bırakır.
tryLock Kaynağa kilitleme işlemi gerçekleştirmeye çalışır. Eğer kaynak kullanılmıyorsa, kilitleyip true döner. Eğer kullanılıyorsa hemen false döner.

NSRecursiveLock yapısının en büyük avantajı, iç içe kullanılabiliyor olmasıdır. Bu sayede, bir thread kendi işini tamamlamadan diğer threadlerin de o kaynağı kullanmasına izin verilir. Bu durumda, iç içe lock yapısının kullanılması gerekiyor. NSRecursiveLock yapısı da bu işlemi en iyi şekilde gerçekleştiriyor.


NSConditionLock

NSConditionLock Nedir?

NSConditionLock, threadler arasında bir veya birkaç threadin geçiş kontrolünde kullanılan bir lock türüdür. NSConditionLock, genellikle bir threadin bir kaynak üzerindeki işlemini tamamlamasını istediğimizde kullanılır. Yani, bir kaynağın belirli bir thread tarafından kullanılmasını sağlamak için kullanılır.

NSConditionLock, bir NSLock gibi kullanılabilir, ancak aynı zamanda bir koşulun yerine getirilmesini bekleyebilir. Diğer bir deyişle, NSConditionLock, iki thread arasındaki etkileşimi yönetmek için koşulların kullanıldığı bir sürümüdür.

NSConditionLock, bir bloğu çalıştırmadan önce belirli bir şarta bağlı olabilir. Bu şart karşılanıncaya kadar, NSConditionLock bekler ve ilgili thread'i duraklatır. Lock serbest kaldığında, sıra bekleyen thread'in çalışmasına izin verir.

NSConditionLock, bir koşulun doğru olmasını beklemek için

  • lock yöntemi

  • unlock yöntemi

  • lockWhenCondition: yöntemi

  • unlockWithCondition: yöntemi

gibi yöntemleri kullanır. NSConditionLock, bir koşulun doğru olduğu anda çalışabilen bir thread'in uyandırılmasına izin verir.

NSConditionLock, genellikle bir thread'in bir kaynak üzerindeki işlemini tamamlamasını istediğimizde kullanılır. Belirli bir condition aşağıdaki şekilde belirlenebilir:

Condition Thread Sayısı
0 1
1 1
2 2
3 3
... ...

Bu özellik, bir kaynak üzerindeki işlemler sırasında beklemenin gerektiği durumlarda oldukça kullanışlıdır. NSConditionLock, thread'in uygun düşen koşulu belirleyebilmesi açısından NSLock'tan biraz farklıdır.


Semaphore

Semaphore, threadler arasında kaynaklara erişimin sınırlandırılması için kullanılan bir senkronizasyon aracıdır. Bölümlere ayırdığımız kodlarda birden fazla thread, aynı anda bir kaynağa erişmek isteyebilir. Bu durumda, semaforlar devreye girer ve her seferinde sadece belirlenen sayıda threadin kaynağa erişmesine izin verir.

Semaphorelar, wait() ve signal() fonksiyonları ile kullanılır. wait() fonksiyonu, semaphore değerini kontrol ederek, değer sıfırsa semaforu kilitleyerek threadin beklemesini sağlar. Signal() fonksiyonu ise bir veya daha fazla blokta bekleyen threadleri belirleyerek onları serbest bırakır.

Semaphorelar, birden fazla kaynak paylaşımında kullanılabilir. Örneğin, bir ağa bağlanan birden fazla threadin aynı anda dosya okuma işlemi yapmaması için semaforlar kullanılabilir. Bu şekilde, sadece belirlenen adet thread aynı anda ağdaki dosyalara erişebilir.

Semaphoreların doğru kullanımı, threadler arasındaki iletişim ve kaynak paylaşımını düzgün bir şekilde kontrol ederek program performansını artırır.


Dispatch Queue'lar

Objective-C'de thread sinyalizasyonu ve senkronizasyon yöntemlerine bakarken, Dispatch Queue'lar da dikkat çeken bir senkronizasyon yöntemidir. Dispatch Queue'lar blok tabanlı multi-threading için kullanılır ve sıralı veya eşzamanlı çalışabilen iki farklı türü vardır. Sıralı işlemleri gerçekleştirmek için serial queue kullanılırken, concurrent queue birden fazla işlemi aynı anda gerçekleştirebilmek için kullanılır. Dispatch queue'lar bloklu bir şekilde çalışır, bu nedenle çok etkilidir ve performans açısından oldukça avantajlıdır.


Serial Queue

=Sıralı işlemleri gerçekleştirmek için kullanılır.

Seri kuyruk, işlemleri sırayla gerçekleştirmek için kullanılır. Bir işlemin bitmeden bir sonraki işleme geçmez. Bu durum, bir sonraki işlemin kaynaklara erişebilmesi için önceki işlemin tamamlanması gerektiği durumlarda çok faydalıdır.

Sıralı kuyruk, tek bir thread tarafından çalıştırılır ve işlemlerin tamamlanma sıralarına göre kontrol edilir. Bu sayede, daha önce başlatılmış olan bir işlem bitmeden başka bir işleme geçilmez. İşlemler tamamen sırayla çalışır ve baştan sona doğru sürekli olarak işlenirler.

Bu kuyrukların oluşturulması oldukça basittir. Öncelikle, kullanmak istediğiniz sıralı kuyruğu oluşturmanız gerekir. Daha sonra, bir blok işlevi tanımlayan kodu kuyruğa eklemeniz yeterlidir. Böylece, işlemler listedeki sıraya göre çalıştırılır ve tamamlanana kadar bir sonraki işleme geçilmez.

Sıralı kuyrukların kullanımı, eşzamanlı işleme ihtiyaç duyulmayan basit işlemlerde oldukça yaygındır. Özellikle, veri sıralama işlemlerinde kullanılırlar. Ayrıca, işlemlerin sırayla çalışması, hata ayıklama sürecinde de oldukça faydalıdır.


Concurrent Queue

Concurrent Queue, aynı anda birden fazla işlemi gerçekleştirmek için kullanılan bir kuyruk yapısıdır. Bu yapıda, işlemler eş zamanlı olarak gerçekleştirilir ve birbirinden bağımsızdır. Bu sayede programların performansı artırılır ve işlemler daha hızlı tamamlanır.

Concurrent Queue, Grand Central Dispatch (GCD) tarafından desteklenir. Bu yapıda, birden fazla thread aynı anda çalışabilir ve hangi thread'in önce işlem yapacağına karar verilir. Böylece, CPU kaynakları daha verimli kullanılır ve yanıt süresi azaltılır.

Bu yapıda, iki tip Queues vardır: global queue ve private queue. Global queue, tüm uygulamalar tarafından erişilebilir ve öncelik sırasına göre işlemleri gerçekleştirir. Private queue, sadece belirtilen uygulamalar tarafından kullanılabilir ve tamamen o uygulama tarafından kontrol edilir.

Concurrent Queue, GCD tarafından otomatik olarak yönetilir ve işlemler arasında senkronizasyon yapılmasına gerek yoktur. Böylece, kod daha basit hale gelir ve hata riski azaltılır.

Tablo olarak aşağıdaki şekilde gösterilebilir:

Türü Görevi
Global Queue Tüm uygulamalar tarafından erişilebilir ve öncelik sırasına göre işlemleri gerçekleştirir.
Private Queue Sadece belirtilen uygulamalar tarafından kullanılabilir ve tamamen o uygulama tarafından kontrol edilir.

Sonuç olarak, Concurrent Queue, aynı anda birden fazla işlemi gerçekleştirmek için kullanılan bir yapısıdır. Bu yapı, programların performansını artırır, CPU kaynaklarını daha etkili kullanmamızı sağlar ve yanıt süresini azaltır. GCD tarafından otomatik olarak yönetildiğinden, kod daha basit hale gelir ve hata riski düşer.


Kilitli Kuyruk Yapıları

Threadler arasındaki iletişim ve senkronizasyonu sağlamak için kullanılan diğer bir araç ise kilitli kuyruk yapılarıdır. Bu yapılar ile threadler arasında mesajlaşma yapmak ve senkronize olmak mümkündür.

Örneğin, Dispatch Group gibi bir yapı ile tamamlanmış işlerin sayısı takip edilebilirken, Dispatch Barrier ile de birden fazla thread arasında eş zamanlı olarak çalışan işlemler arasına bariyer eklemek mümkündür.

Ayrıca, Dispatch Queue'lar içindeki işlemlerin hangi sıra ile gerçekleştirileceği de kilitli kuyruk yapıları kullanılarak belirlenebilir. Örneğin, bir Serial Queue kullanarak sıralı işlemler gerçekleştirilebilirken, Concurrent Queue ile aynı anda birden fazla işlem yapılabilir.

Bunların yanı sıra, Dispatch Group gibi bir yapı ile tamamlanmış işlerin sayısı takip edilebilirken, Dispatch Barrier ile de birden fazla thread arasında eş zamanlı olarak çalışan işlemler arasına bariyer eklemek mümkündür.

Genel olarak, kilitli kuyruk yapıları yardımı ile threadler arasında senkronizasyon ve iletişim çok daha kolay hale gelmektedir.


Dispatch Group

Dispatch Group, tamamlanmış işlerin sayısını takip eden bir mekanizmadır. Parametre olarak verilen işleri bir gruba dahil eder ve bu işlerin tamamlanma durumunu kontrol eder. Bu sayede, birden fazla threadin çalıştığı senaryolarda tüm işlerin tamamlandığını anlamak kolaylaşır.

Dispatch Group, asenkron bir şekilde çalışan işlemleri kontrol etmek için kullanılabilir. Örneğin, dosya indirme işlemlerinin tamamlanmasını beklemek istediğimiz senaryolarda Dispatch Group kullanılabilir. Bu sayede, dosya indirme işlemleri farklı threadlerde asenkron bir şekilde gerçekleştirilebilir ve bir dispatch group içinde toplanabilir. Tüm dosya indirme işlemlerinin tamamlandığı kontrol edilip, diğer işlemlere geçilmesi sağlanabilir.

Aşağıdaki tablo, Dispatch Group kullanımını özetlemektedir:

Metot Açıklama
dispatch_group_create() Yeni bir Dispatch Group oluşturur.
dispatch_group_enter() Dispatch Group'a bir işlem eklendiğini belirtir.
dispatch_group_leave() Dispatch Group'dan bir işlem çıkarıldığını belirtir.
dispatch_group_wait() Tüm işlemelerin tamamlanmasını bekler.
dispatch_group_notify() Tüm işlemler tamamlandığında belirtilen blok çalıştırılır.

Yukarıdaki metotlardan en önemlisi dispatch_group_notify() metotudur. Bu metot, Dispatch Group içindeki işlemlerin tamamlandığını tespit ettiğinde belirtilen bloğu çalıştırır ve işlemlerin tamamlanmasından sonra yapılması gereken işlemlerin gerçekleştirilmesini sağlar.


Dispatch Barrier

Dispatch Barrier: Dispatch Barrier, Grand Central Dispatch (GCD) kuyruklarının bloklardan oluşan bir yapıdır. Barrier blok, kuyruğun çalışmasını durdurarak, kuyrukta sonraki blokların çalışmasını engeller. Tüm işlemlerin tamamlanması beklenir ve barrier bloğunun çalışmasına izin verilir. Bu, özellikle kaynak paylaşımının yapıldığı durumlarda kullanışlıdır.

Barrier bloğu, sıralı veya eş zamanlı kuyruklarda kullanılabilir. Eğer bir kuyrukta birden fazla thread varsa ve sıralama önemli ise, barrier bloğu kullanılarak, tüm çalışan işlemlerin bitmesi beklenir ve sıralı olarak barrier bloğunun çalışması sağlanır. Öte yandan, Concurrent kuyruklarla eş zamanlı çalışan işlemlerde, barrier bloğu farklı bir kullanıma sahiptir.

Mesela, bir uygulamanın giriş sayfasında, birden çok resim yüklenmek isteniyor olsun. Bu resimlerin tamamı bağımsız olarak yüklenebilir. Ancak sonrasında, yüklenmiş tüm resimlerin işlenmesi ve görüntülenmesi için tüm işlemler tamamlanmış olmalıdır. Bu durumda, Concurrent kuyruklarla işlemler çalıştırılır ve en sonunda bir barrier bloğu kullanılarak, tüm işlemler bittikten sonra ekrana gösterim yapılır.