- aAynı process içindeki thread'ler process'in belleğini paylaşır; ayrı process'lerin normalde kendi address space'leri vardır✓
- bProcess'ler belleği varsayılan olarak paylaşır; her thread izole bir address space alır
- cHem thread'ler hem process'ler her zaman tamamen izole bellek alır
- dThread'ler yalnızca aynı CPU core üzerinde çalıştıklarında bellek paylaşır
Backend Eşzamanlılık Mülakat Soruları
Eşzamanlılık mülakatlarının çekirdeği: paylaşılan ve değişebilen state, concurrency-parallelism ayrımı, kilit maliyetleri, deadlock/livelock/starvation, ölçekte çıkan tuzaklar ve bellek görünürlüğü.
Gerçek simülasyonu dene →Konu özeti
Bu konu neyi ölçer
Eşzamanlılık soruları "hangi API'yi biliyorsun"u değil, paylaşılan ve değişebilen state'i doğru yönetip yönetemediğini ölçer. Mülakatta en sık dönülen nokta şudur: senkronizasyon yalnızca state hem paylaşılan hem mutable olduğunda gerekir. Her çağrıya ait yerel değişkenler ve açılıştan sonra hiç değişmeyen veri, istenildiği kadar thread'den serbestçe okunabilir — immutability, güvenli eşzamanlılığın en ucuz stratejisidir.
Karıştırılan temel ayrımlar
- Concurrency ve parallelism aynı şey değildir. Concurrency yapıyla ilgilidir: yaşam süreleri örtüşen işleri çevirmek. Parallelism yürütmeyle ilgilidir: işlerin aynı anda gerçekten çalışması, ki bu birden çok core ister. Tek core'lu bir makine concurrent olabilir, ama asla gerçekten parallel olamaz.
- Process ve thread. Bir process kendi adres alanına sahiptir; thread'leri o belleği paylaşır. Bu paylaşım iletişimi ucuzlatan şeydir — ve shared-state bug'larını mümkün kılan da aynı şeydir.
- Critical section, paylaşılan state'e dokunan bölgedir; adındaki "critical" performansla ilgili değildir. O bölgenin çalışması karşılıklı dışlamalı hale getirilmelidir.
- Race condition bir exception türü değildir. Tanımlayıcı özelliği zamanlamaya bağımlılıktır: aynı girdiler, işlemlerin iç içe geçişine göre farklı sonuçlar üretir. Sonuçları genellikle sessizce bozar; tehlikeli olmasının sebebi budur.
Klasik örnek counter = counter + 1. Kaynak kodda tek satır olması bir şey ifade etmez: bu bir read-modify-write dizisidir, iki thread okuma ile yazma arasında iç içe geçtiğinde bir artış diğerini ezer ve kaybolur.
Araçlar ve gerçek maliyetleri
Mutex yalnızca tek şey vaat eder: aynı anda tek sahip. Preemption'ı engellemez — kilidi tutan thread bölümün ortasında duraklatılabilir; diğerleri sadece o bırakana kadar giremez. Geri alma semantiği lock'ların değil transaction'ların işidir.
Tek global lock doğruluğu contention pahasına satın alır: yük altında thread'ler kilitte kuyruğa girer, core'lar boş durur. Kilidin maliyeti arkasındaki veri miktarından değil, onu isteyen thread sayısından gelir. Çare daha ince taneli kilitler ya da daha az paylaşılan mutable state'tir.
Read-write lock okuma-ağırlıklı ve okuma bölümleri uzun olan yüklerde kazandırır. İki bilinen maliyeti vardır: writer starvation (okuyucular sürekli gelirken yazar süresiz bekleyebilir; birçok implementasyon writer preference ekler) ve ekstra defter tutma — çok kısa bölümlerde düz mutex genellikle daha ucuzdur.
Counting semaphore "aynı anda en çok N" için doğru primitive'dir. Mutex bunun için aşırı kısıtlar; sleep ile bekletmek istek hızını şekillendirir, eşzamanlılığı değil.
Pessimistic ve optimistic locking. Pessimistic çakışmayı olmadan dışlar (SELECT ... FOR UPDATE), eşzamanlı yazarlar bekler. Optimistic herkesi ilerletir, çakışmayı yazma anında yakalar (tipik olarak WHERE version = ?), kaybeden retry eder. Düşük çekişmede optimistic, sıcak ve yüksek çekişmeli kayıtlarda pessimistic avantajlıdır — retry fırtınasını önler.
İlerlemenin durduğu üç biçim
- Deadlock bir döngü gerektirir: her thread bir kilidi tutarken diğerini ister. Herkes kilitleri aynı sırayla (önce A sonra B) alırsa döngü kurulamaz.
- Livelock'ta thread'ler bloklanmış değil meşguldür: koridorda birbirine yol vermeye çalışan iki insan gibi retry edip dururlar.
- Starvation bir adalet sorunudur: aç kalan thread sırası gelse ilerleyebilir, ama CPU'yu ya da kilidi hep başkalarına kaptırır.
Deadlock ve livelock güvenilir şekilde kendiliğinden çözülmez; randomized backoff ya da tutarlı kilit sıralaması gibi bir tasarım düzeltmesi gerekir.
Ölçekte ortaya çıkan tuzaklar
- Hot row. Satır kilidi transaction commit edene kadar tutulur; her istek tek bir sayaç satırının arkasına kuyruğa girer ve sistem seri bir boru hattına döner. Çare sayacı shard'lamak (N satır, rastgele artır, okurken topla) ya da artırımı asenkron toplamaya taşımaktır.
- Thread pool deadlock. Parent task'lar tüm thread'leri kaplayıp aynı pool'da koşacak subtask'ları beklerse, ortada tek bir mutex olmadan deadlock oluşur. Çözüm: subtask'ları ayrı pool'da koşturmak, bloklamamak ya da uçuştaki parent sayısını pool boyutunun altında tutmak.
- Event loop'u CPU işiyle tıkamak. Event loop işler arasında ancak kontrolü bıraktıkları noktada geçiş yapar. Saf CPU işi kontrolü hiç bırakmaz; bitene kadar tüm bağlantılar durur. Standart çözüm o işi worker'lara taşımaktır.
- Thread saymadan ölçeklemek. Sekiz core en fazla sekiz CPU-bound thread çalıştırır; 800 thread arasında geçiş yapmak CPU'yu gerçek iş yerine context switch'e harcar.
- False sharing. Aynı cache line'ı paylaşan bağımsız değişkenler, coherence protokolü altında line'ı core'lar arasında ping-pong eder: mantıksal olarak bağımsız, fiziksel olarak çekişmeli. Çare padding ya da thread-local biriktiriciler.
Görünürlük: kilit her şey değil
Doğruluk yalnızca karşılıklı dışlamadan ibaret değildir; yazmaların görünürlüğü ayrı bir konudur. happens-before senkronizasyon kenarlarıyla kurulur: aynı mutex'in sonraki kilidinden önce sıralanan bir unlock, acquire-load tarafından okunan bir release-store, ya da thread start/join. Yalnızca aynı adresi paylaşmak sıralama değil, data race yaratır; wall-clock sırası ise bir senkronizasyon kenarı olmadan anlamsızdır.
Bunun pratik iki sonucu:
- Güvenli publication. Yarı kurulmuş bir nesnenin referansı diğer thread'lere sırasız görünebilir. Referansı release-acquire semantiği taşıyan bir atomic üzerinden kaydet ya da dilin garantili tek-seferlik initialization olanağını kullan.
- Condition variable'da
while. Uyanma bir kanıt değil ipucudur: spurious uyanmalar mümkündür ve gerçek bir sinyalden sonra bile uyanan thread mutex'i yeniden almak zorundadır — o boşlukta başka bir consumer öğeyi alabilir. Koşulu döngüde yeniden kontrol etmek evrensel sözleşmedir.
Mükerrerlik ve idempotency
Eşzamanlılık sorularının sık bir dalı "aynı iş iki kez yapıldı" vakasıdır. Client tarafındaki devre dışı buton, retry'ları ve ikinci sekmeyi baypas ettiği için hiçbir şey garanti etmez; pencereyi küçültmek de yarışı ortadan kaldırmaz. Garanti sunucuda yaşamalıdır: her deneme için bir token, unique constraint altında saklanır ve tekrarlara orijinal sonuç döndürülür. Durum geçişlerinde aynı işi koşullu update yapar: UPDATE ... SET status = 'paid' WHERE id = ? AND status = 'pending' ve yalnızca gerçekten satır değiştiyse devam et.
Mülakatta anlatabilmen gerekenler
- Bu state gerçekten paylaşılan ve mutable mı; değilse senkronizasyona hiç gerek yok.
- Seçtiğin primitive neyi vaat ediyor, neyi vaat etmiyor (mutex preemption'ı engellemez).
- Kilit granülerliğinin contention'a etkisi ve tek global kilidin faturası.
- Deadlock'u nasıl imkânsız kıldığın (tutarlı sıralama, tek kilit, sınırlı uçuş sayısı).
- Yazmaların diğer thread'lerde ne zaman görünür olduğu.
- Aynı isteğin iki kez gelmesi hâlinde sistemin ne yapacağı.
Bu özet, aşağıdaki soruların doğrulanmış açıklamalarından çıkarıldı.
Örnek sorular
- aTamamen aynı anlama gelirler — iki kelime tek kavramın birbirinin yerine geçen adlarıdır
- bConcurrency, yaşam süreleri örtüşen birden çok işi yönetmektir; parallelism, aynı fiziksel anda birden fazlasını çalıştırmaktır✓
- cParallelism tek core'da bile olabilir; concurrency ise her zaman birden çok core gerektirir
- dConcurrency sadece I/O-bound işlere, parallelism sadece CPU-bound işlere uygulanır
counter = 0
function handle_request():
counter = counter + 1İki thread,
handle_request() fonksiyonunu 1000'er kez çağırıyor. counter'ın son değeri ne olur?- aHer zaman tam 2000 — artırma tek satır olduğu için kesintiye uğrayamaz
- bHer zaman tam 1000 — ikinci thread'in yazmaları ilkinin yazmalarını ezer
- cEn fazla 2000, ama daha az olabilir — eşzamanlı artırmalar race condition'a kurban gidebilir✓
- dProgram çöker, çünkü iki thread aynı değişkene yazamaz
counter = counter + 1 bir read-modify-write dizisidir. İki thread okuma ile yazma arasında iç içe geçtiğinde artışlardan biri diğerini ezer ve kaybolur; sonuç 2000'in altına düşebilir. Kaynak kodda tek satır olması (a) bir şey ifade etmez — birden çok adıma derlenir — ve eşzamanlı yazma tek başına programı çökertmez (d).- aProgramın performans açısından en kritik, her şeyden önce optimize edilmesi gereken sıcak yolu
- bPaylaşılan bir kaynağa erişen ve aynı anda yalnızca tek thread'de çalışması gereken kod✓
- cUygulama açılışında, henüz hiçbir thread yokken çalışan kod
- dYakalanmayan exception'ları tüm process'i düşüren blok
- aAynı anda en fazla bir thread onu tutar; lock ile unlock arasındaki kod asla eşzamanlı çalışmaz✓
- bKoruduğu kod tek bir CPU core'a sabitlenir ve hızlanır
- cThread lock'u tuttuğu sürece OS scheduler onu preempt etmez
- dÇakışan yazmalar otomatik tespit edilip geri alınır
thread 1: thread 2:
lock(A) lock(B)
lock(B) lock(A)
# ... iş ... # ... iş ...
unlock(B) unlock(A)
unlock(A) unlock(B)Bu iki thread aynı anda çalıştığında ne olabilir?
- aHer zaman tamamlanırlar, çünkü lock istekleri ilk gelen alır sırasıyla verilir
- bRuntime hatası oluşur: aynı mutex iki farklı thread'den istenemez
- cLivelock: iki thread iş yapmadan sonsuza dek lock alıp bırakır
- dDeadlock mümkündür: her thread bir lock'u tutarken diğerini sonsuza dek bekleyebilir✓
Uzmanlığa göre
Kıdeme göre
Diğer alanlar
3750 soruluk Backend bankasında kendini sına.
Mülakata başla