Python'da Eş Zamanlılık / Concurrency in Python: Threads & Coroutines (TR/ENG)
Tags: python, coroutines, threads, programming
Published: 2023-06-03 14:49:59

**(TR)**
Eş zamanlılık, bir veya birden fazla işlemin aynı anda veya yakın zamanlarda yürütülmesine verilen addır. Eş zamanlılık esasına dayalı sistemler, bir işlem ünitesine birden fazla iş yaptırmak üzerine kurgulanır. Eş zamanlılık işlemleri paralel yürütmeye yarar fakat tam olarak paralel programlama değildir. Her bir işlem genelde birbirinden bağımsızdır. Eş zamanlılık, işlemci kaynaklarını etkin ve verimli şekilde kullanmamıza olanak sağlar. 

Bu yöntemin verimlilik, kullanıcı deneyimi ve performans gibi konulara bir çok katkısı vardır. Örneğin; 

- Cpu-time'ı yoğun bir işi işlemci kaynaklarına dağıtmak,
- Kullanıcı ile etkileşime girilen anlarda hızlı yanıt vermek,
- I/O işlemi yapılırken verimliliği arttırmak ve işlemlerin bloklanmaması,

gibi konularda avantaj sağlar. Eş zamanlılık getirdiği avantajların yanı sıra bazı kritik dezavantajlar da sunar. Eş zamanlı bir iş bütünü oluştururken program akışı, veri sağlamlığı ve birbirini etkileyen işlemlerin kusursuz çalışmasını sağlamak çok daha fazla dikkat ve vakit ister. Sıralı(Sequential) programlamadakinden çok daha farklı hata durumları meydana gelebilir.

#Threads

Thread'ler, işletim sistemi seviyesinde eş zamanlı çalışmayı sağlamak için kullanılan, tek işlem içinde yürütülen bağımsız ve paralel işleme birimleridir. Bir işlem birden fazla thread içerebilir. Threadler bellek üzerinde aynı yeri paylaşır ve bu nedenle birbirleri ile veri paylaşabilirler.

`threading` modülü ile Python'da thread oluşturmak ve yönetmek için kullanılan builtin bir modüldür. `threading.Thread` sınıfından türetilen bir alt sınıf ile `run` metodu çağırılır ve içerisine çalışacak kod bloğu verilir. Start verdikten sonra thread'ımız artık çalışıyor duruma gelir.

Thread'ler bazı durumlarda performansı artırabilir. Özellikle uzun süren işlemler, ağ istekleri ve giriş/çıkış operasyonları gibi durumlarda. Ancak dikkatli kullanılmalıdır çünkü birden fazla thread aynı kaynaklara erişebilir ve senkronizasyon sorunlarına yol açabilir. Bundan dolayı thread'leri kullanırken uygun senkronizasyon mekanizmaları ve thread güvenliği sağlamak önemlidir. Örnek olarak, Lock (kilit), Semaphore (sayaç), Event (olay) ve Condition (koşul) gibi senkronizasyon araçları threading modülünde bulunur. Bu araçlar, thread'ler arasındaki uyumu sağlamak için kullanılır.

Thread kullanırken dikkat edeceğimiz bir diğer konu ise GIL(Global Interpreter Lock) terimidir. GIL, Python programımızın thread güvenliğini sağlar ve paylaşılan verilere aynı anda erişimi kontrol altında tutar. GIL, aynı anda sadece bir thread'in çalışmasına izin verir. Bu sebeple Python'da threadler _**paralel olarak**_ yürütülemez. 


#Coroutines

Coroutine, eş zamanlı programlamada kullanılan yapılardır. Bir fonksiyonun istenen kısmında askıya alınıp, işlem sırasını diğer coroutine'e aktarabilen ve sonrasında kaldığı yerden devam eden fonksiyonlardır. Python'da coroutine'leri asyncio modülü ile birlikte kullanabiliriz. asyncio modülü ise `async/await` sözdizimiyle eş zamanlı kod yazmamızı destekleyen bir kütüphanedir. 

Coroutine'ler genelde asenkron I/O işlemleri, ağ tabanlı işlemler, veritabanı etkileşimleri gibi uzun süren ve bloklanma eğilimli işlemlerde kullanılır.


#Coroutine ve Thread Farkları

İkisi de birbirine benzer işlemler gerçekleştirir fakat bazı temel farklar vardır:

1. **Verimlilik**: Coroutine'lerin, thread'lere kıyaslara daha lightweight bir yapısı vardır çünkü coroutine'ler ayrı bir işlem oluşturmaz, aynı işlem içerisinde çalışır. Bu da coroutine'lerin daha az sistem kaynağı tüketmesini ve daha hızlı işleme alınmasını sağlar.

2. **İş Birliğine Dayalı Yürütme**: Coroutine'ler kendi arasında işbirliğine dayalı yürütme modeli kullanır. Bir coroutine, diğer coroutine'e kontrolü aktardığında beklemeye alınır ve coroutine'ler arasında anlaşmalı şekilde kontrol aktarımı yapılır.

3. **Senkronizasyon Ve İletişim**: Coroutine'ler `await` anahtar kelimesiyle başka coroutine işlemlerine geçiş yapabilir veya bekleyebilir. Yani işlemler arası senkronizasyon thread'e göre daha kolaydır. Fakat thread'ler yukarda da bahsettiğimiz gibi daha çok iş parçacığı mekanizması(kilit, sayaç vs.) kullanır. 

4. **Deterministik Yürütme**: Coroutin'lerde kodun akışı ve yürütme sırası belli olduğundan daha deterministik olmasını sağlar. Yani deterministik yürütme olması için işlemin girdisi ve çıktısı, programın diğer akışından bağımsız olmalıdır. Thread'lerde ise örneğin bir değişken iki farklı thread tarafından işleme alınıyorsa, `threading.Lock()` mekanizması ile ilgili değişken için bir kilitleme mekanizması oluşturmamız gerekebilir. Bu da thread kullanırken daha dikkatli olmamız gerektiğini gösterir.


---


**(ENG)**

Concurrency, refers to the execution of one or more tasks simultaneously or in close proximity. Systems based on the principle of concurrency are designed to perform multiple tasks on a single processing unit. Concurrency enables parallel execution of tasks but it is not strictly parallel programming. Each task is generally independent of each other. Concurrency allows us to utilize processor resources effectively and efficiently.

This method has many contributions to efficiency, user experience, and performance. For example:

- Distributing a CPU-time task to processor resources.
- Providing quick responses during user interactions.
- Increasing efficiency during I/O operations and preventing blocking of tasks.

Concurrency brings advantages in these areas. Along with the advantages, it also presents some critical disadvantages. Creating a concurrent system requires much more attention and time to ensure the smooth operation of program flow, data integrity, and interdependent tasks. It can lead to different error scenarios compared to sequential programming.

#Threads

Threads are independent and parallel processing units used to enable concurrent execution at the operating system level. A process can contain multiple threads. Threads share the same memory space, allowing them to share data with each other.

The threading module in Python is a built-in module used to create and manage threads. It is a built-in module for creating and managing threads in Python. It uses a subclass derived from the `threading.Thread` class, where the `run` method is called and the code block to be executed is provided. Once started, our thread is in a running state.

Threads can improve performance in certain situations, especially for long-running tasks, network requests, and i/o operations. However, they should be used carefully because multiple threads can access the same resources and lead to synchronization problems. Therefore, it is important to ensure proper synchronization mechanisms and thread safety when using threads. The threading module provides synchronization tools such as Lock, Semaphore, Event, and Condition to ensure coordination between threads.

Another consideration when using threads is the Global Interpreter Lock (GIL). The GIL ensures thread safety in Python programs and keeps access to shared data under control. The GIL allows only one thread to execute at a time. Therefore, threads in Python cannot be executed in parallel.

#Coroutines

Coroutines are structures used in concurrent programming. They are functions that can be suspended at a desired point and transfer the execution flow to another coroutine, then resume from where they left off. In Python, we can use coroutines with the `asyncio` module. The `asyncio` module is a library that supports writing concurrent code using the `async/await` syntax.

Coroutines are commonly used for long-running and potentially blocking operations such as asynchronous I/O operations, network-based operations, and database interactions.

#Differences Between Coroutines and Threads

Both coroutines and threads perform similar tasks, but there are some fundamental differences:

1- **Efficiency**: Coroutines have a more lightweight structure compared to threads because coroutines do not create a separate process, they run within the same process. This allows coroutines to consume fewer system resources and perform faster.

2- **Collaborative Execution**: Coroutines use a cooperative execution model among themselves. When a coroutine yields control to another coroutine, it goes into a waiting state, and control is transferred among coroutines based on an agreement.

3- **Synchronization and Communication**: Coroutines can switch to other coroutine tasks or wait for them using the `await` keyword. Thus, synchronization between tasks is easier compared to threads. However, threads primarily rely on thread mechanisms such as locks, counters, etc., for synchronization.

4- **Deterministic Execution**: Coroutines have a more deterministic execution flow since the sequence of code execution is well-defined. To achieve deterministic execution, the input and output of a task should be independent of the program's other flows. In threads, for example, if a variable is accessed by two different threads, we may need to create a locking mechanism using `threading.Lock()` for that variable. This shows that more caution is needed when using threads.