Concurrency Modelleri: Event-based Concurrency vs. Dataflow Concurrency

Herkese selamlar. Bu yazı, bir önceki yazının devamı şeklinde olacaktır.
Bu yazıda hem event-based hem de dataflow concurrency konularına değineceğiz.
Event-based Concurrency Nedir?
Aslında bu concurrency yaklaşımından bahsedeceksek, olayın kahramanı “eventler” oluyor. Event-based concurrency, özellikle etkileşimin yoğun olduğu sistemlerde, parallelism gerektiren sistemlerde ve kullanıcı etkileşimli uygulamalarda yaygın olarak kullanılan bir yaklaşımdır.
Biraz spesifik örnekler vermemiz gerekir mi? Evet gerekir 😛
Event-based concurrency’nin temel amacı, sistemde gerçekleşen bazı durumlara, tetiklenen event’lere hızlı bir şekilde yanıt verebilmektir. Bir event, genellikle bir durum değişikliği, bir kullanıcı etkileşimi ya da bir mesajın alınması gibi bir süreçlerle birlikte oluşabilir. Bu event’lar, genellikle bir event source (örneğin, GUI, network işlemi ya da x bir durum) tarafından tetiklenir.
Event-based concurrency, event’lere yanıt veren işlemleri concurrent olarak çalıştırarak verimlilik, ölçeklenebilirlik ve esneklik sağlayabilir. Bir event’in tetiklenmesiyle ilgili işlemler, diğer işlemleri etkilemeden aynı anda gerçekleştirilebilir.
Senaryo 1:
Örneğin, bir web sunucusu düşünelim. Web sunucusu bir HTTP isteği aldığında, bu bir event olarak kabul edilir.
Sunucu, bu isteği işlemek için ayrı bir thread oluşturabilir ve diğer istekleri aynı anda karşılamaya devam edebilir. Bu sayede, bir isteği işlerken diğer isteklerin bloklanmasının da önüne geçilir ve daha iyi ölçeklenebilirlik elde edilir.
Senaryo 2:
Bu örnekte ise bir masaüstü uygulaması düşünelim. Mesela Spotify’ın desktop app’i. Bu bir grafiksel arayüze sahip uygulamadır.
Yani GUI uygulama. Kullanıcı play butonuna tıkladığı anda, bu bir event olarak kabul edilir. GUI, bu olaya karşı tepki olarak müzik çalmaya başlar. Bu işlemi gerçekleştirmek için GUI, main thread dediğimiz ana iş parçacığını bloke etmeden ayrı bir thread üzerinde çalışabilir ve kullanıcının diğer etkileşimlere tepki vermesini sağlar.
Peki akıcılık bizim için önemli. Fakat her şey mükemmel mi? Tabiiki değil. Bu yaklaşımda da bazı sorunlar mevcut.
Avantajları Nelerdir?
- Eşzamanlılık: Event-based concurrency, aynı anda birden çok event’e yanıt verebilme yeteneği sağlar. Bu, sistemlerin daha verimli çalışmasını ve daha hızlı tepki sürelerini sağlar.
- Ölçeklenebilirlik: Event-based concurrency, process’lerin bağımsız olarak çalışmasını sağladığı için ölçeklenebilirliği artırır. Event’lere tepki veren process’ler, gerektiğinde paralel olarak çalışabilir ve daha fazla yükü karşılayabilir.
- Esneklik: Event-based concurrency, farklı türdeki event’leri ve bunlara yanıt veren process’leri kolayca yönetmeyi sağlar. Bu, sistemlerin esnek ve genişletilebilir olmasını sağlar.
Dezavantajları
- Karmaşıklık: Event-based concurrency, event’leri ve event’lere tepki veren process’leri yönetmek için daha fazla kod karmaşıklığı gerektirebilir. Concurrency ilgili senkronizasyon ve veri bütünlüğü sorunları burada da yer alabilir.
- Error Handling: Event’ler arasında iletişim ve koordinasyon sağlamak karmaşık olabilir. Concurrency ilgili hataların izlenmesi, debugging gibi konuların yönetimi zor olabilir.
Bir dizi sorunu da burada örnek olarak sunabiliriz
Sorunlar
- Race Condition: Birden fazla thread’in aynı veriye aynı anda erişmeye çalışması durumunda race condition ortaya çıkabilir. Bu durumda, veri bütünlüğünün sağlanmasına dair sorunlar ve beklenmedik sonuçlar oluşabilir.
- Deadlock: Eğer thread’ler bir işlemden dolayı birbirini beklerse ve hiçbiri işini yapamaz ise deadlock durumu oluşabilir. Bu durumda, sistemdeki kaynakları verimli bir şekilde kullanmak mümkün olmayabilir.
- Starvation: Bazı thread sürekli olarak diğerlerinden daha az kaynak alırsa starvation durumu oluşabilir. Bu durumda, bazı thread’ler sürekli olarak geri planda kalır ve işlem yapma fırsatı bulamazlar.
Bu konuya dair bir örnek kod parçacığını şöyle bırakabilirim;
using System;
using System.Threading;
public class Program
{
public static void Main()
{
EventExample eventExample = new EventExample();
Thread eventThread = new Thread(eventExample.ProcessEvent);
eventThread.Start();
eventExample.TriggerEvent("Hello world!!!");
Thread.Sleep(1000);
eventExample.StopEventProcessing();
Console.WriteLine("Application terminated");
}
}
public class EventExample
{
public event EventHandler EventOccurred;
private bool continueProcessing = true;
public void TriggerEvent(string message)
{
OnEventOccurred(message);
}
protected virtual void OnEventOccurred(string message)
{
EventOccurred?.Invoke(this, message);
}
public void ProcessEvent()
{
EventOccurred += HandleEvent;
while (continueProcessing)
{
Console.WriteLine("Event is processing...");
Thread.Sleep(500);
}
EventOccurred -= HandleEvent;
}
private void HandleEvent(object sender, string message)
{
Console.WriteLine($"Event received: {message}");
}
public void StopEventProcessing()
{
continueProcessing = false;
}
}
Bu örnek, EventExample class’ı içerisinde bir event tanımladık ve bu event’i tetiklemek için TriggerEvent metodundan yardım aldık. ProcessEvent metodu, event processing yapan bir thread olarak çalışır ve HandleEvent metoduyla birlikte event’in alınmasını ve işlenmesini sağlar. StopEventProcessing metodu ise event processing işlemini durdurmak için kullanılır.
Yukarıdaki örnekte, event tetiklendikten sonra event processing yapan thread tarafından alınıp işlenir. Bu süreç, concurrent olarak gerçekleşir ve event processing işlemi durdurulana kadar devam eder.
Dataflow Concurrency Nedir?
Dataflow concurrency, veri akışını temel alan bir concurrency modelidir. Bu modelde, bir program veya sistemdeki process’ler, dataflow üzerinden yönetilir ve birbirlerinden bağımsız bir şekilde çalışabilir. Bu yaklaşım, paralel programlamaya bir alternatif olarak sunulabilir.
Dataflow concurrency modelinde, process’ler birbirleriyle iletişim kurmak yerine, veri akışını yani dataflow’u paylaşırlar. Her process, bir input alır, belirli bir process’in çalışmasını sağlar ve bize bir output sunar. Bu output, başka process’ler tarafından input olarak kullanılabilir. Yani şöyle bir örnek saçma gibi görünse de akışı anlatıyor bize;
var response = MyFunction(anInput);
var response2 = MyFunction2(response);
Process’ler, inputları aldığı anda çalışmaya başlar ve output değeri elde edilince diğer process’lerin çalışmasını sağlarlar. Bu şekilde, process’ler birbirlerini beklemek zorunda kalmadan concurrent olarak çalışabilir.
Yukarıdaki kod örneği lütfen kafanızda “aaa böyle çalışıyormuş” gibi bir şeyi kesin olarak canlandırmasın.
Dataflow concurrency, paralel programlamayı daha kolay ve esnek hale getirmek için var. Process’ler, datanın bağımlı olduğu process’e göre otomatik olarak planlanır. Yani bir geliştirici örnekteki gibi işlemlerin hangi sırada olduğunu bilmek ya da bu işlemler paralel çalışırken ayrıca işlemlerin paralel sırasını belirlemek için de ekstra efor sarfetmez.
Avantajları
- Paralelizm ve Performans İyileştirmeleri: Dataflow concurrency modelinde işlemler veri akışına bağlı olarak otomatik olarak sıralanır. Bu sayede paralel işlemlerin etkin bir şekilde gerçekleştirilmesi de sağlanır. Bu sayede de yapılan işlemin performansı artabilir ve daha hızlı sonuç üretilebilir.
- Esneklik: Dataflow concurrency modeli, işlemlerin birbirlerine bağımlı olmadan çalışabilmesini sağlar. Bu, programı veya sistemi esnek hale getirir, çünkü işlemler bağımsız olarak yürütülebilir ve değişiklikler yapmak veya yeni işlemler eklemek daha kolay hale gelir.
- Dataflow’un Anlaşılabilirliği: Dataflow concurrency modeli, işlemlerin veri akışını temsil eder. Bu, programın veya sisteminin veri akışını anlamak ve takip etmek için daha kolay bir yol sunar. Veri akışı temsili, kodun daha anlaşılır ve bakımı daha kolay hale gelmesini sağlar.
Dezavantajları / Sorunları
Tabiiki her güzel şeyin bir de kötü yanı vardır. Burada, dataflow modelde yaşanabilecek olası sorunlar ya da dezavantajlar ise şunlardır;
- Veri Bağımlılığı Yönetimi: Dataflow concurrency modeli, işlemler arasındaki veri bağımlılıklarını doğru bir şekilde yönetmek gerektirir. Doğru bir sıralama sağlanmadığında veya yanlış veri bağımlılıkları tanımlandığında, program veya sistem hatalara veya yanlış sonuçlara neden olabilir.
- İşlem Planlaması: Dataflow concurrency modeli, işlemlerin otomatik olarak planlandığı bir sistem gerektirir. Bu planlama süreci, büyük ve karmaşık programlar veya sistemler için zorlu olabilir. İşlemlerin veri bağımlılıklarını doğru bir şekilde analiz etmek ve uygun bir sıralama oluşturmak zaman alabilir ve karmaşık olabilir.
- Veri İletişimi ve Senkronizasyon: Dataflow concurrency modelinde, işlemler veri akışı üzerinden iletişim kurar. Bu, veri paylaşımı ve senkronizasyonu gerektirebilir. İşlemler arasındaki veri akışının düzgün bir şekilde sağlanması ve veri paylaşımının güvenli olması için uygun mekanizmaların uygulanması önemlidir. Aksi takdirde, veri bütünlüğü sorunları veya eşzamanlılık hataları ortaya çıkabilir.
- Paralelizm Maliyeti: Dataflow concurrency modeli, paralelizm için işlemlerin parçalanması ve dağıtılması gerektirebilir. Bu, bazı durumlarda ek maliyetlere neden olabilir.
Bu konuya dair bir örnek kod ise şöyle olabilir;
using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
class Program
{
static async Task Main(string[] args)
{
int[] input = { 1, 2, 3, 4, 5 };
var options = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount
};
var transformBlock = new TransformBlock(async num =>
{
await Task.Delay(100);
return num * num;
}, options);
var actionBlock = new ActionBlock(result =>
{
Console.WriteLine(result);
}, options);
transformBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true });
foreach (var num in input)
{
await transformBlock.SendAsync(num);
}
transformBlock.Complete();
await actionBlock.Completion;
Console.WriteLine("Process completed...");
}
}
Yukarıdaki kodu şöyle açıklayabilirim;
TransformBlock ve ActionBlock kullanarak dataflow örnekleri oluşturuyoruz. TransformBlock kullanıcıdan alınan verinin karesini alıyor.
ActionBlock sonuçları işleyerek ekrana yazdırır yani. MaxDegreeOfParallelism propertysi ise, paralel çalışacak işlem sayısı ayarlamak için kullanılır. Hazırladığımız örnekte ise işlemcinin çekirdek sayısına eşitledik.
SendAsync metodu, kullanıcıdan alınan veriyi TransformBlock’a post etmek için kullanılır. Complete metodu, kullanıcıdan alınan tüm verinin başarılı bir şekilde TransformBlock’a yollandığını belirtir ve Completion property’si, hepsinin başarılı bir şekilde tamamlanmasının beklenmesini sağlar.
Yazı bu kadardı. Okuduğunuz için teşekkür ederim.
Kafada kurgulaması ve gerçek dünya uygulaması olmadan anlaşılması zor bir konu. Okudukça yeni şeyler de öğreniyorum. Umarım bana olduğu kadar hatta daha fazlası olacak şekilde size de faydası olur.
Eksik, hatalı bir şey var ise şimdiden özür dilerim :)
Kaynaklar
- https://berb.github.io/diploma-thesis/original/055_events.html
- https://stackoverflow.com/questions/17059225/what-is-event-driven-concurrency
- https://itnext.io/solving-concurrency-in-event-driven-microservices-79bbc13b597c
- http://gpars.org/0.12/guide/guide/7.%20Dataflow%20Concurrency.html
- https://doc.akka.io/docs/akka/1.2/java/dataflow.html
- https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library
- https://www.youtube.com/watch?v=XlEQeHoVLrc