---
title: 'Muteks ve Okuma/Yazma Kilidi'
source: 'https://academia.sh/tr/kurslar/rust-eszamanlilik/muteks-ve-okuma-yazma-kilidi'
course: 'Eşzamanlılık ve Asenkron Rust'
language: tr
updated: '2026-09-13T05:09:28+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Kilidi tutan tarafın kendi başına, kilidi bekleyen tarafın başkası bitince ilerlediğini bir kanal el sıkışmasıyla belirlenimci biçimde ölçer'
    - 'İki bekleyen taraf arasında hangisinin önce gireceğinin ölçülmediğini ve neden ölçülmediğini gösterir'
    - 'Okuma/yazma kilidinin okuma tarafının aynı anda birden fazla okuyucuya izin verdiğini koşturarak doğrular'
---

# Muteks ve Okuma/Yazma Kilidi

Bir dışlama kilidini tutan kendi başına ilerler, onu bekleyen başkası bitince ilerler; bu sıra bir kanal el sıkışmasıyla belirlenimci kılınır ve okuma/yazma kilidinin okuma tarafı, aynı anda birden fazla okuyucuya izin vererek dışlama kilidinden ayrılır.

Önceki ders paylaşılan bir tam sayı üzerinde iki iş parçacığının sırayla çalıştığını, ama bu
sırayı birleştirme çağrılarıyla kendisinin zorladığını gösterdi: `birinci` tamamen bitmeden
`ikinci` hiç başlamadı. Bu iki işlemin aynı anda çalışıp yalnızca paylaşılan veriye erişim
anında sıraya girmesi gerektiğinde, birleştirme yeterli bir araç değil — iki iş parçacığının
geri kalanı paralel ilerlerken yalnızca kritik bölgeye giriş anlarının sıraya girmesi
gerekiyor. Bu ders bekleyen bir tarafın kilit üzerinden nasıl ilerlediğini doğrudan ölçüyor.

Bu derste iki farklı kilit türü koşturuluyor: `Mutex`, tek bir tarafın aynı anda erişmesine
izin veren bir dışlama kilidi; `RwLock`, okuma ve yazma erişimini ayrı ayrı ele alan bir
okuma/yazma kilidi. İkisi de aynı temel soruna çözüm sunuyor — paylaşılan bir veriye
eşzamanlı erişimi güvenli kılmak — ama izin verdikleri paylaşımın derecesi farklı. Ders bu
farkı üç ayrı senaryoyla ölçüyor: kilidi tutan tarafın kendi başına ilerlemesi, kilidi
bekleyen tarafın başkası bitince ilerlemesi ve okuma/yazma kilidinin okuma tarafının bu
kuralı nasıl gevşettiği.

## Kilidi Tutan Kendi Başına, Bekleyen Başkası Bitince

Bir dışlama kilidi kavramı burada yeniden kurulmuyor. İşletim Sistemi Kavramları kursu
kilidin ne için var olduğunu, JVM, Eşzamanlılık ve Başarım kursu bir kilidi tutan iş
parçacığının kimliğini ölçtü. Bu kurs farklı bir soru soruyor: kilidi **tutan** tarafın ve
kilidi **bekleyen** tarafın ilerlemek için neye ihtiyaç duyduğu. Yanıt kısa: kilit boşken
çağrılan bir `lock` kimseye ihtiyaç duymadan döner — kendi başına sınıfına girer. Kilit
doluyken çağrılan bir `lock` ise, kilidi tutan taraf onu bırakana kadar bekler — başkası
bitince sınıfına girer. Bu, Kanallarla Mesajlaşma dersindeki `recv` çağrısıyla aynı yapıya
sahip: ikisi de kendi dışında bir olayın gerçekleşmesini bekliyor, farkları beklenen olayın
bir gönderim mi yoksa bir serbest bırakma mı olması.

Kilidin kendisi, `lock` çağrısının döndürdüğü bir tutamaçla temsil ediliyor. Bu tutamaç
kapsam dışına çıktığında (ya da açıkça bırakıldığında) kilit otomatik olarak serbest kalıyor
— Sahiplik ve Bellek Modeli kursunun kurduğu bırakma kuralının bir uzantısı. Aşağıdaki
programda `tutan` iş parçacığının içindeki `kayit` adlı tutamaç, işlevin gövdesi bitip
kapsam dışına çıkana kadar kilidi elinde tutuyor; kilidin ne zaman bırakılacağı programcının
açık bir çağrısına değil, bu tutamacın ömrünün nerede bittiğine bağlı. Bu, önceki derste
görülen taşınan bir değerin sahiplik kuralıyla aynı düzenek — burada uygulanan yer kilit
tutamacı.

Bu ilişkiyi çalışma zamanının gerçek zamanlamasına bakmadan ölçmek gerekiyor, çünkü
zamanlama ortama bağlıdır ve bu kursta basılmıyor. Aşağıdaki program, kilidi tutan tarafın
kilide gerçekten dokunduğunu ve bekleyen tarafın gerçekten ondan sonra girdiğini bir kanal
el sıkışmasıyla kanıtlıyor: bekleyen taraf, tutan tarafın kilide girdiğine dair bir sinyal
almadan denemeye bile başlamıyor ve iki tarafın da paylaşılan bir deftere yazdığı kayıtların
sırası, kilidin kendisi tarafından zorlanıyor. Bu el sıkışma olmasaydı — yani `bekleyen`
kendi kilit çağrısını herhangi bir sinyal beklemeden yapsaydı — ölçüm hâlâ doğru sırayı
üretirdi, çünkü kilit zaten mutlak bir sıra garantisi veriyor; ama o durumda `bekleyen`in
çağrısının gerçekten `A` kilidi tutuyorken mi yoksa ondan önce mi yapıldığı belirsiz
kalırdı. El sıkışma, ölçümün **hangi anı** kanıtladığını netleştiriyor.

## El Sıkışmayla Belirlenimci Sıra

Aşağıdaki program üç ayrı senaryoyu peş peşe koşturuyor ve üçü de aynı `main` işlevi
içinde, kendi kanallarıyla izole ediliyor. İlk senaryo tek bir bekleyeni ölçüyor ve iki ayrı
sinyal kanalı kullanıyor: `a_icerde_gonder`/`a_icerde_al`, `tutan`ın kilide girdiğini
`bekleyen`e bildiriyor; `devam_gonder`/`devam_al` ise `bekleyen`in denemeye başladığını
`tutan`a bildiriyor. Bu iki yönlü el sıkışma, hangi tarafın kilide ne zaman dokunduğunu
kesinleştiriyor — kilidin kendisi (`gunluk`) bu sinyalleşmeden habersiz, yalnızca standart
`lock` çağrısıyla korunuyor.

```rust
// kilit.rs — kilidi tutan kendi basina ilerler, kilidi bekleyen baskasi bitince ilerler.

use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex, RwLock};
use std::thread;

fn main() {
    // --- disari cikma sirasi el sikismayla belirlenimci kilinir ---
    let gunluk = Arc::new(Mutex::new(Vec::<String>::new()));
    let (a_icerde_gonder, a_icerde_al) = channel::<()>();
    let (devam_gonder, devam_al) = channel::<()>();

    let g = Arc::clone(&gunluk);
    let tutan = thread::spawn(move || {
        // kendi basina: kilit bostu, kimseyi beklemeden aliniyor
        let mut kayit = g.lock().unwrap();
        kayit.push(String::from("A-girdi"));
        a_icerde_gonder.send(()).unwrap();
        devam_al.recv().unwrap(); // B'nin denemeye basladigindan emin olunana kadar tutuluyor
        kayit.push(String::from("A-cikti"));
        // kayit burada kapsam disina cikip birakiliyor
    });

    a_icerde_al.recv().unwrap();
    let g = Arc::clone(&gunluk);
    let bekleyen = thread::spawn(move || {
        devam_gonder.send(()).unwrap();
        // baskasi bitince: A kilidi birakana kadar burada bloklanir
        let mut kayit = g.lock().unwrap();
        kayit.push(String::from("B-girdi"));
        kayit.push(String::from("B-cikti"));
    });

    tutan.join().unwrap();
    bekleyen.join().unwrap();

    println!("tek bekleyen sirasi: {:?}", *gunluk.lock().unwrap());

    // --- iki bekleyen: kimin once girecegi olculmez, yalniz ikisinin de girdigi olculur ---
    let gunluk2 = Arc::new(Mutex::new(Vec::<String>::new()));
    {
        let mut ilk = gunluk2.lock().unwrap();
        ilk.push(String::from("baslangic"));
    }

    let g = Arc::clone(&gunluk2);
    let c = thread::spawn(move || {
        let mut kayit = g.lock().unwrap();
        kayit.push(String::from("C-girdi"));
    });
    let g = Arc::clone(&gunluk2);
    let d = thread::spawn(move || {
        let mut kayit = g.lock().unwrap();
        kayit.push(String::from("D-girdi"));
    });
    c.join().unwrap();
    d.join().unwrap();

    let mut girenler: Vec<String> = gunluk2
        .lock()
        .unwrap()
        .iter()
        .filter(|s| s.ends_with("-girdi"))
        .cloned()
        .collect();
    girenler.sort();
    println!("iki bekleyenin girdigi kayitlar (sira olculmedi): {:?}", girenler);

    // --- okuma/yazma kilidi: birden fazla okuyucu ayni anda kilidi tutabiliyor ---
    let deger = Arc::new(RwLock::new(42));
    let (r1_icerde_gonder, r1_icerde_al) = channel::<()>();
    let (r2_tamam_gonder, r2_tamam_al) = channel::<()>();
    let (birak_gonder, birak_al) = channel::<()>();

    let d1 = Arc::clone(&deger);
    let okuyucu1 = thread::spawn(move || {
        let tutamac = d1.read().unwrap();
        r1_icerde_gonder.send(()).unwrap();
        birak_al.recv().unwrap();
        *tutamac
    });

    r1_icerde_al.recv().unwrap();
    let d2 = Arc::clone(&deger);
    let okuyucu2 = thread::spawn(move || {
        // R1 hala okuma kilidini tutuyorken bu cagri da basariyla kilit aliyor
        let tutamac = d2.read().unwrap();
        let sonuc = *tutamac;
        r2_tamam_gonder.send(()).unwrap();
        sonuc
    });

    r2_tamam_al.recv().unwrap();
    birak_gonder.send(()).unwrap();

    let v1 = okuyucu1.join().unwrap();
    let v2 = okuyucu2.join().unwrap();

    println!();
    println!("iki okuyucu ayni anda kilit tuttu, okunan degerler: {} ve {}", v1, v2);
    println!("cok okuyana izin verildi: {}", v1 == v2);
}
```

```
tek bekleyen sirasi: ["A-girdi", "A-cikti", "B-girdi", "B-cikti"]
iki bekleyenin girdigi kayitlar (sira olculmedi): ["C-girdi", "D-girdi"]

iki okuyucu ayni anda kilit tuttu, okunan degerler: 42 ve 42
cok okuyana izin verildi: true
```

**IP6.** İlk satırdaki dörtlü sıra üç ayrı koşumda birebir aynı çıkıyor:
`A-girdi, A-cikti, B-girdi, B-cikti`. Bu sıra zamanlamadan değil, programın yapısından
geliyor. `bekleyen` iş parçacığı, `tutan` kilide girdiğine dair sinyali (`a_icerde_al.recv()`)
almadan kendi kilit çağrısını hiç yapmıyor; `tutan` ise kendi kaydını (`A-girdi`) yazıp
sinyali gönderdikten **sonra**, `bekleyen`in denemeye başladığına dair bir onay
(`devam_al.recv()`) bekliyor ve ancak o onay geldikten sonra ikinci kaydını (`A-cikti`)
yazıp kilidi bırakıyor. Bu üç adımlı el sıkışma, `bekleyen`in kilit çağrısının gerçekten `A`
kilidi elinde tutarken yapıldığını garanti ediyor — `bekleyen` bu çağrıda bloklanıyor,
`tutan` kilidi bırakana kadar ilerleyemiyor. Bloklanma süresi ölçülmüyor, yalnızca sonucun
sırası: `B` girişleri hiçbir zaman `A` çıkışından önce görünmüyor.

## İki Bekleyen: Sıra Ölçülmez

İkinci senaryoda `C` ve `D` aynı kilidi bekliyor. İkisi de sonunda giriyor — çıktı bunu
kanıtlıyor — ama **hangisinin önce** girdiği hiçbir yerde ölçülmüyor. Program bu belirsizliği
gizlemiyor, tersine yapısına gömüyor: kayıtlar toplanmadan önce sıralanıyor, tıpkı İş
Parçacığı Oluşturma dersindeki rapor kanalının yaptığı gibi. `girenler` listesi her koşumda
`["C-girdi", "D-girdi"]` olarak çıkıyor — bu, alfabetik sıralamanın sonucu, gerçek giriş
sırasının değil.

Bu senaryoda `C` ve `D` başlamadan önce günlüğe bir `"baslangic"` kaydı yazılıyor ve bu
kayıt raporlanan listede görünmüyor, çünkü `girenler` yalnızca `-girdi` ile biten satırları
süzüyor. Bu süzme bilinçli: `gunluk2` içinde tuttuğu kayıtların tam sırası aslında bellidir
ve deterministiktir (`baslangic` her zaman ilk sırada), ama `C-girdi` ile `D-girdi`
arasındaki göreli sıra bu kesinliği taşımıyor. Süzme, ölçülebilecek olanla ölçülemeyecek
olanı aynı veri yapısı içinde ayırıyor.

## Okuma/Yazma Kilidi: Çok Okuyana İzin

Üçüncü senaryo bir dışlama kilidiyle bir okuma/yazma kilidi arasındaki farkı doğrudan
gösteriyor. `okuyucu1`, okuma kilidini alıp elinde tutarken bir sinyal gönderiyor;
`okuyucu2`, bu sinyali aldıktan **sonra** aynı değere kendi okuma kilidini istiyor. Eğer bu
kilit bir dışlama kilidi olsaydı, `okuyucu2` `okuyucu1` kilidi bırakana kadar bloklanırdı —
ve `okuyucu1`, `okuyucu2`nin bir onay göndermesini beklediği için, bu onay hiçbir zaman
gelmez ve program kilitlenirdi. Program kilitlenmeden tamamlandığına göre, `okuyucu2`nin
okuma kilidi çağrısı gerçekten `okuyucu1` hâlâ kilidi tutuyorken başarıyla dönmüş demektir.

Bu, ilk senaryodaki el sıkışmayla aynı mantığı tersine çeviriyor. Orada el sıkışma bir
bekleyişi kanıtlamak için kullanıldı: `bekleyen`in kilidi gerçekten `tutan` elindeyken
istediğini göstermek. Burada ise el sıkışma bir bekleyişin **yokluğunu** kanıtlamak için
kullanılıyor: eğer okuma çağrıları birbirini bloklasaydı, program bu el sıkışmayı hiçbir
zaman tamamlayamaz ve sonsuza kadar beklerdi. Programın normal biçimde bitmesi, kendi
başına okuma/yazma kilidinin okuma tarafının paylaşılabilir olduğunun kanıtı — bu derste
kullanılan ölçüm yöntemlerinden en dolaylısı, ama en kesini.

**IP7.** İki okuyucunun okuduğu değer birebir aynı (`42` ve `42`), çünkü ikisi de aynı
paylaşılan sayıyı okuyor ve okuma sırasında hiçbir yazma gerçekleşmiyor. Bu ölçüm, okuma/
yazma kilidinin okuma tarafının **kendi içinde** paylaşılabilir olduğunu gösteriyor: iki
okuyucu aynı anda kilide sahip olabiliyor, oysa aynı senaryo bir `Mutex` ile kurulsaydı
`okuyucu2` kendi kilidini isteyemeden `okuyucu1`in onayını asla gönderemeyeceği için program
kilitlenirdi.

Bu farkın kaynağı, okuma/yazma kilidinin iki ayrı giriş noktası sunması. Bir dışlama kilidi
tek bir çağrı taşır — kim çağırırsa çağırsın aynı kurala tabidir. Okuma/yazma kilidi ise okuma
ve yazma için ayrı çağrılar taşır: okuma çağrısı, kilitte hiçbir yazıcı yokken ne kadar
okuyucu isterse kabul eder; yazma çağrısı ise her zaman **tek başına** kalmayı ister, ne
başka bir yazıcıyla ne de bir okuyucuyla paylaşmaz. Bu ölçümde yalnızca okuma tarafı
koşturuldu; yazma çağrısının davranışı, dışlama kilidininkiyle özdeş — bir yazıcı kilide
girdiğinde, o an kilitte bekleyen her taraf (ister okuyucu ister yazıcı) onun bitmesini
bekler.

## Sınırlayıcı Ölçüm

Kilit **veriyi** korur, **sırayı** kurmaz. Bir dışlama kilidi ya da bir okuma/yazma
kilidinin yazma tarafı, aynı anda yalnızca bir tarafın kritik bölgeye girmesini garanti
eder — bu derste ölçülen budur. Ama iki bekleyenden **hangisinin** önce gireceği, kilidin
kendi sözleşmesinin bir parçası değil; bu, işletim sisteminin zamanlayıcısına, kilidin iç
uygulamasına ve o anki yüke bağlı bir ayrıntıdır. `C` ve `D` örneğinde bu bilinçli olarak
ölçülmedi ve basılmadı — kayıtlar toplanmadan önce sıralanarak, hangi sıranın gerçekleştiği
sorusu programın çıktısından tamamen çıkarıldı.

Bu sınır, Muteks ve Okuma/Yazma Kilidi arasındaki farkı da netleştiriyor: okuma/yazma
kilidinin okuma tarafı birden fazla bekleyene aynı anda giriş **izni** veriyor, ama bu izin
de bir sıra garantisi taşımıyor — üçüncü bir okuyucu eklenmiş olsaydı, o okuyucunun ne zaman
gireceği yine ölçülmezdi. Kilit çözdüğü tek sorun, paylaşılan veriye erişimin ne zaman
**güvenli** olduğu; hangi tarafın önce erişeceği sorusunu hiç çözmüyor.

Bu ayrım rastgele bir seçim değil, kilidin verdiği garantinin doğal sınırı. Bir kilit, kaç
tarafın kritik bölgede aynı anda bulunabileceğini (dışlama kilidinde bir, okuma/yazma
kilidinin okuma tarafında sınırsız) tanımlıyor; kritik bölgeye giriş **sırasının** neye göre
belirleneceğini tanımlamıyor, çünkü bu genellikle işletim sistemi zamanlayıcısının kendi
politikasına bırakılan bir karar. Bu kursta hiçbir yerde bir zamanlayıcı politikasının adı
geçmiyor ve geçmeyecek; ölçülen yalnızca kilidin **verdiği** ve **vermediği** garantiler.

## Özet

- Kilidi tutan taraf kendi başına ilerler; kilidi bekleyen taraf, kilit bırakılana kadar
  başkası bitince ilerler. Bu ilişki bu derste bir kanal el sıkışmasıyla belirlenimci
  biçimde kanıtlandı.
- İki bekleyen taraf arasında hangisinin önce gireceği ölçülmez ve basılmaz; kayıtlar
  toplanmadan önce sıralanarak bu belirsizlik programın çıktısından çıkarılır.
- Okuma/yazma kilidinin okuma tarafı, aynı anda birden fazla okuyucuya izin verir — bu,
  aynı senaryonun bir dışlama kilidiyle kilitlenmeye yol açacağı bir durumla karşılaştırıldı.
- Kilit paylaşılan veriye erişimi korur, ama hangi tarafın önce gireceğine dair hiçbir
  garanti vermez; bu iki şey birbirinden bağımsızdır.
- JVM, Eşzamanlılık ve Başarım kursu bir kilidi hangi iş parçacığının tuttuğunu ölçtü, bu
  kurs aynı kilidi bekleyen tarafın ilerlemek için neye ihtiyaç duyduğunu ölçüyor.

## Sonraki Adım

Kilit bekleyen tarafı durduruyor, çünkü paylaşılan veriye erişim sırayla verilmek zorunda.
Bu derste görülen üç senaryonun hepsinde bir tarafın kilit için beklemesi gerekti — ya kilit
tutulduğu için (`bekleyen`, `C`, `D`), ya da bir yazma isteği söz konusu olsaydı (okuma/
yazma kilidinin yazma tarafı). Ama bazı paylaşımlar bu kadar sıkı bir korumaya ihtiyaç
duymuyor — özellikle paylaşılan değer tek bir sayı gibi basitse ve üzerinde yapılan işlem
donanımın kendisinin tek adımda gerçekleştirebileceği kadar küçükse. Sıradaki ders, hiç
kimsenin beklemediği bir paylaşım biçimini ölçüyor: bir sayacın kilitsiz, atomik işlemlerle,
kimseye ihtiyaç duymadan ilerlemesi — ve bunun kilitli bir sayaçla aynı sonucu verip
vermediği.
