PythonProgramlamaKıdemli Python Geliştirici

**CPython** yorumlayıcısının yaşam döngüsünün hangi aşamasında **asyncio** olay döngüsü kendine ait boru bildirim kanalını oluşturur ve bu mimari seçiminin `call_soon_threadsafe` yurt dışı iş parçacıklarından çağrıldığında yarış koşullarını nasıl önlediği?

Hintsage yapay zeka asistanı ile mülakatları geçin

Sorunun yanıtı

Kendine ait boru bildirim kanalı, SelectorEventLoop'un (Unix sistemlerinde varsayılan) başlatma aşamasında oluşturulur. Özellikle, boru oluşturma, call_soon_threadsafe'in ilk çağrılması veya döngünün yapıcısı sırasında, CPython sürümüne bağlı olarak tembel bir biçimde gerçekleşir. Tarihsel olarak, Python'un asyncio modülü, 3.4 sürümünde birleşik bir asenkron G/Ç çerçevesi sağlamak için tanıtıldı ve yerleşik Unix ağ uygulamalarından "kendine ait boru numarası" tekniğini ödünç aldı. Bu teknik, bir bloklayıcı seçiciyi dış bir iş parçacığından çağırmadan uyandırma gibi temel bir sorunu çözer.

Temel sorun, olay döngüsünün çoğu zaman select() sistem çağrısında (veya epoll/kqueue eşdeğerlerinde) dosya tanımlayıcılarının hazır hale gelmesini bekleyerek engelli olarak geçirmesidir. Eğer başka bir iş parçacığı döngünün iç kuyruğuna yalnızca bir geri çağırma eklerse, seçici bilgilendirilmez ve sonsuza dek uyur, bu da geri çağırmanın gecikmesine sebep olur. Bu, zaman açısından hassas güncellemelerin, döngü ağ G/Ç'sini beklerken asla çalışmama riskini yaratan bir yarış koşulu oluşturur.

Bu yarış koşulunu önlemek için, olay döngüsü bir Unix borusu (veya Windows üzerinde bir soket çifti) oluşturur ve okucuyu seçiciye kaydeder. call_soon_threadsafe çağrıldığında, geri çağırmayı güvenli bir şekilde bir iş parçacığına duyarlı kuyruğa eklemek için bir kilit alır, ardından borunun yazma ucuna bir bayt yazar. Bu yazma işlemi, seçiciyi hemen uyandırır ve olay döngüsünün yeni geri çağırmayı doğru iş parçacığı bağlamında işleme almasını sağlar ve veri bozulmasını önler.

Hayattan bir durum

Ana asyncio olay döngüsünün borsa bağlantılarını yönetip canlı bir emir defterini güncellediği yüksek frekanslı bir ticaret platformunu düşünün. Bir işçi iş parçacığı, portföy pozisyonları üzerinde CPU yoğun Monte Carlo risk hesaplamalarını paralel olarak gerçekleştirir. Bir işçi iş parçacığı bir hesaplamayı tamamladığında ve ticaret durumunu—örneğin bir emri iptal etmeyi—güncellemesi gerektiğinde sorun ortaya çıkar.

Bir potansiyel çözüm, belirli zaman aralıklarında kuyruğu periyodik olarak kontrol eden bir asyncio görevi ile birlikte queue.Queue kullanmaktır. Bu yaklaşım iş parçacıklarını ayırır ancak anlık bildirimler için kabulleniş sürelerini artırdığı ve iş kontrolü için CPU döngülerini boşa harcadığı için kabul edilemez gecikmelere neden olur. Ayrıca, en uygun kontrol sıklığını belirlemek, yanıt verme ve kaynak tüketimi arasında bir denge kurmayı gerektirir.

Bir diğer çözüm, işçi iş parçacığından doğrudan loop.call_soon() kullanmaktır; ancak bu iş parçacığına duyarlı değildir ve iç geri çağırma kuyruğunu bozabilir veya çalışma zamanı hatalarına neden olabilir. CPython'ın olay döngüsü yapıları eşzamanlı erişime karşı koruma sağlamadığı için potansiyel çöküşlere veya kaybolan güncellemelere yol açar. Bu yaklaşım, döngünün iç durumunun yalnızca döngüyü çalıştıran iş parçacığından değiştirilmesi varsayımını ihlal etmektedir.

Seçilen çözüm, kendine ait boru mekanizmasını kullanarak seçiciyi hemen uyandıran loop.call_soon_threadsafe()'ı kullanır. Bu, risk güncellemelerinin mikro saniyeler içinde borsa ile iletilmesini sağlarken, iş parçacığı güvenliğini de korur ve döngü kontrolü ile ilgili sorunları önler. Sonuç, hesaplama geri testinin G/Ç bağlı ticaret mantığı ile paralel olarak çalıştığı ve engelleme veya yarış koşulları olmadan istikrarlı bir sistemdir.

Adayların sık sık gözden kaçırdığı şeyler

call_soon_threadsafe neden sıradan bir işlevi kabul eder de bir coroutine'yi değil ve geliştiricilerin, iş parçacıklarından asenkron görevleri planlamak için kodlarını nasıl uyarlamaları gerektiği nedir?

call_soon_threadsafe geri çağırmaları—normal çağrılabilir nesneleri—planlar, çünkü olay döngüsünün iç kuyruğu geri çağırmaları hemen işlerken coroutine'ler, create_task ile görev oluşturulmasını gerektirir. Geliştiriciler bunun yerine asyncio.run_coroutine_threadsafe(coro, loop) kullanmalıdır; bu, coroutine'i bir asyncio.Task içine sarar ve güvenli bir şekilde planlar. Bu yöntem, içten içe call_soon_threadsafe kullanarak görevi döngüye ekler, ancak ayrıca çağıran iş parçacığının sonuçları beklemesine veya istisnaları kontrol etmesine olanak tanıyan bir concurrent.futures.Future döner ve iş parçacığı tabanlı ile coroutine tabanlı yürütme modelleri arasında köprü kurar.

Kendine ait boru mekanizması, çoklu iş parçacıkları yüksek çekişim sırasında call_soon_threadsafe'i aynı anda çağırdığında "gürültücü sürü" senaryosunu nasıl yönetir?

Mutex ile korunan iç kuyruk, geri çağırmaların düzenli bir şekilde eklenmesini sağlarken, birçok iş parçacığının aynı anda boruya yazması teorik olarak yarış koşullarına neden olabilir. Ancak, CPython uygulaması, tek bir baytın bloklamayan yazımını kullanır ve olay döngüsü tüm boru tamponunu tek bir okuma geri çağrısında boşaltır. Küçük boyutlardaki (PIPE_BUF'un altında, tipik olarak Linux'ta 4KB) boru yazımları OS düzeyinde atomik olduğundan, çoklu yazıcılar baytları birbirine karıştırmaz ve olay döngüsü tek bir uyandırmadan sonra tüm bekleyen geri çağırmaları işleyerek bildirimleri etkili bir şekilde toplar.

Bir geliştirici call_soon_threadsafe'i olay döngüsü kapandıktan sonra kullanmaya çalışırsa ne tür özel bir hata modu oluşur ve bu neden temel bir yaşam döngüsü ihlali olarak kabul edilir?

Bir kez loop.close() çağrıldığında, olay döngüsü seçicisini kapatır ve kendine ait boru dosya tanımlayıcılarını kapatır; sonraki call_soon_threadsafe çağrıları, bu yöntem döngünün _closed bayrağını kontrol ederken iç kilidi tutarak RuntimeError hatası fırlatır. Bu, yöntemin döngünün çalışma veya hazır durumda geçerli dosya tanımlayıcılarıyla olduğunu varsaydığı için bir yaşam döngüsü ihlali anlamına gelir; kapalı bir boruya yazmaya çalışmak, OS düzeyinde OSError veya BrokenPipeError hatasına yol açar. Açık kontrol, belirsiz davranışları önler ve geliştiricilere, döngüyü kapatmadan önce iş parçacıklarını durdurmak için uygun kapatma senkronizasyonu implementasyonu gerektirdiğini ve kritik temizlik görevlerini korumak için asyncio.shield kullanması gerektiğini bildirir.