---
title: 'İş Parçacıkları Arası Güvenliğin Tipteki Karşılığı'
source: 'https://academia.sh/tr/kurslar/rust-eszamanlilik/is-parcaciklari-arasi-guvenligin-tipteki-karsiligi'
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:
    - 'Bir değerin iş parçacığı sınırını geçebilmesinin derleme zamanında tipte denetlenen bir koşul olduğunu açıklar'
    - 'Geçebilen bir tasarımı koşturur ve geçemeyen bir tipin neden reddedildiğini düzyazıda tarif eder'
    - 'Tipin izin verdiği bir paylaşımın bağımsız varsayımdan farklı bir sonuç verebileceğini koşturarak gösterir'
---

# İş Parçacıkları Arası Güvenliğin Tipteki Karşılığı

Bir değerin bir iş parçacığı sınırını geçip geçemeyeceği tipte yazılı bir koşuldur: geçebilen bir tasarım gerçekten geçiriliyor ve kaydı okunuyor, ama tipin izin vermesi paylaşılan bir hesabın bağımsız varsayımla aynı sonucu vereceğini göstermiyor.

Önceki ders bir değerin bir iş parçacığından diğerine geçebildiğini iki örnekle gösterdi:
bir liste, bir ileti yapısı. İkisi de sorunsuzca geçti, ama bu geçişin kendisi hiç
sorgulanmadı — sanki her tip bir kanaldan ya da bir paylaşılan kaba geçebilirmiş gibi
davranıldı. Aslında geçemeyen tipler var ve bu izin çalışma zamanında değil **derleme
zamanında** kontrol ediliyor. Bu dersin sorusu şu: bir tipin sınırı geçebilmesi neye bağlı
ve bu koşul kaynakta nerede yazılı.

JVM, Eşzamanlılık ve Başarım kursu veri yarışı kavramını kurdu: iki iş parçacığının aynı
belleğe eşzamanlı ve en az biri yazarak eriştiği, sonucun tanımsız olduğu durum. Bu kavram
burada yeniden tanımlanmıyor. Rust'ın farkı, bu riski çalışma zamanında bir saptayıcıyla
yakalamak yerine, riski taşıyan tipleri iş parçacığı sınırını geçmekten **baştan** alıkoyması.
Bir değerin veri yarışına açık olup olmadığı, o değer paylaşılmadan önce, kaynak derlenirken
biliniyor.

## `Send` ve `Sync`: Tipte Yazılı Bir İzin

Sahiplik ve Bellek Modeli kursu sahiplik, ödünç alma ve referans kavramlarını kurdu, ama o
kurs bu kavramları tek bir iş parçacığı varsayımıyla anlattı. Bir değerin başka bir iş
parçacığına **taşınabilmesi** ile bir değere başka bir iş parçacığından **referans
verilebilmesi**, ayrı iki koşuldur ve Rust bu ikisini iki ayrı tipe bağlıyor: `Send` ve
`Sync`. Bir tip `Send` ise, o tipten bir değerin sahipliği başka bir iş parçacığına
güvenle taşınabilir. Bir tip `Sync` ise, o tipe bir referans (`&T`) başka bir iş
parçacığından güvenle okunabilir — yani `T`, `Sync` ise `&T` de `Send`dir.

Bu ikisi çevrilmeyen tip adlarıdır ve çoğu tip için derleyici tarafından **otomatik**
kurulur: bir yapının bütün alanları `Send` ise yapının kendisi de `Send`dir, aynı kural
`Sync` için de geçerli. Programcı bu iki tipi genellikle hiç yazmaz; yalnızca bir değeri bir
iş parçacığı sınırının üzerinden geçirmeye çalıştığında, derleyici o değerin tipinin bu
izne sahip olup olmadığını kontrol eder. İzin yoksa kaynak derlenmez — ama bu kısıt bu
derste bir hata olarak koşturulmuyor, yalnızca düzyazıda tarif ediliyor.

Bu otomatik kurulma, önceki iki dersin örneklerinin neden hiç sorgulanmadan geçtiğini de
açıklıyor. Önceki dersteki `Vec<i32>` ve `Ileti` yapısı — bir dizge ve bir tam sayı alanı
taşıyan basit bir yapı — hem `Send` hem `Sync`. Bunun nedeni, ikisinin de yalnızca temel
tiplerden ve standart kitaplığın kendisi `Send`/`Sync` işaretli koleksiyonlarından kurulmuş
olması. Bir tipin bu izinlerden birini **kaybetmesi** için, içinde özel bir paylaşım
düzeneği barındırması gerekiyor — tam da bir sonraki bölümde görülecek `Rc` ve `RefCell`
gibi. Derleyici bu izni tip tanımına bakarak çıkarıyor; programcı `Send` ya da `Sync`
sözcüğünü kaynakta hiç yazmasa da, her `struct` ve `enum` bildirimi örtük olarak bu iki
sınıflandırmadan birine giriyor.

## Geçebilen Tasarım

Sahiplik ve Bellek Modeli kursunun Atomik Referans Sayımı dersi `Arc`ı tanıttı, ama o
derste ölçülen yalnızca sayacın atomik olup olmamasıydı — sınır geçme sorusu orada açıkça
dışarıda bırakılmıştı. Bu ders o sınırı ölçüyor: `Arc`ın atomik sayacı, onu hem `Send` hem
`Sync` yapan koşulun ta kendisi. İçindeki tip de bu iki izne sahipse (`Mutex<T>` her zaman
`Send`dir ve içindeki `T`, `Send` ise `Mutex<T>` de `Sync`dir), `Arc<Mutex<T>>` bütünüyle
iş parçacığı sınırını geçebilen bir tip oluyor.

Aşağıdaki program, alanlarının hepsi `Send` olan bir yapıyı `Arc<Mutex<...>>` içine
yerleştirip iki iş parçacığı arasında paylaşıyor. `Arc` hem `Send` hem `Sync`dir (içindeki
tip de öyleyse), `Mutex` de aynı şekilde — bu yüzden `kayit` değeri her iki iş parçacığına
da klonlanan bir tutamaçla güvenle geçebiliyor.

```rust
// sinir.rs — bir degerin is parcacigi sinirini gecebilmesi tipte yazili bir kosuldur.

use std::sync::{Arc, Mutex};
use std::thread;

struct PaylasilanKayit {
    etiket: String,
    deger: i32,
}

fn main() {
    let kayit = Arc::new(Mutex::new(PaylasilanKayit {
        etiket: String::from("olusum"),
        deger: 5,
    }));

    let k1 = Arc::clone(&kayit);
    let birinci = thread::spawn(move || {
        let mut icerik = k1.lock().unwrap();
        icerik.deger += 1;
        icerik.etiket = String::from("birinci-yazdi");
    });
    birinci.join().unwrap();

    let k2 = Arc::clone(&kayit);
    let ikinci = thread::spawn(move || {
        let icerik = k2.lock().unwrap();
        (icerik.deger, icerik.etiket.clone())
    });
    let (okunan_deger, okunan_etiket) = ikinci.join().unwrap();

    println!("sinir gecti — deger: {}, etiket: {}", okunan_deger, okunan_etiket);
}
```

```
sinir gecti — deger: 6, etiket: birinci-yazdi
```

**IP4.** `birinci` iş parçacığı kilit boşken çağrıldığı için kimseye ihtiyaç duymadan
ilerliyor — kendi başına sınıfına giriyor. `ikinci` iş parçacığı da aynı şekilde başlıyor,
ama onun asıl bağımlılığı `birinci.join()` çağrısının ana akışta önce tamamlanmış olması:
program ikinci klonu göndermeden önce ilk iş parçacığını birleştiriyor, böylece okunan
değerin `birinci`nin yazdığı değer olduğu güvenceye bağlanıyor. Kayıt tipinin sınırı
geçebilmesi, bu iki iş parçacığının aynı veriye sırayla erişebilmesinin ön koşulu; koşulun
kendisi çalışma zamanında değil, `Arc<Mutex<PaylasilanKayit>>` yazıldığı anda, derleme
zamanında sağlanıyor.

`Arc::clone` çağrısının kendisi de dikkat çekici: `k1` ve `k2`, aynı `Mutex`e işaret eden
iki ayrı tutamaç, ama ikisi de aynı öbek nesnesini paylaşıyor. Bu, önceki dersteki kanal
tutamacının klonlanmasıyla aynı düzenek — `Arc::clone` da veriyi kopyalamıyor, yalnızca
paylaşılan sahiplik sayacını bir artırıyor. Aradaki fark, kanalın klonlanan ucunun bir
gönderim kapısı olması, `Arc`ın klonlanan ucunun ise doğrudan paylaşılan veriye erişim
noktası olması. `kayit.lock()` çağrısı bu noktada devreye giriyor: `Arc` paylaşılan
erişimi mümkün kılıyor, `Mutex` ise o erişimi aynı anda yalnızca bir tarafa veriyor. İkisi
ayrı sorunları çözüyor — biri "kaç tarafın bu veriye referansı var" sorusunu, öbürü "şu anda
kim yazabilir" sorusunu.

## Geçemeyen Tip: Neden `Rc<RefCell<..>>` Sınırı Aşamaz

Sahiplik ve Bellek Modeli kursunun paylaşımlı sahiplik payı `Rc` ve `RefCell` çiftini tek iş
parçacıklı bir bağlamda tanıttı: `Rc`, bir değerin birden fazla sahibi olmasını sayarak
izliyor, `RefCell` ise ödünç kurallarını derleme yerine çalışma zamanında denetliyor. Bu
ikisi birlikte kullanıldığında `Rc<RefCell<T>>`, aynı `sinir.rs`deki `Arc<Mutex<...>>` gibi
paylaşılan ve değiştirilebilir bir kap gibi görünüyor — ama bu kap bir iş parçacığı
sınırını geçemez.

Sebep sayaçların kendisinde. `Rc`nin tuttuğu referans sayısı, iki iş parçacığı aynı anda
artırıp azaltabilseydi bir veri yarışına açık olurdu, çünkü bu sayaç atomik değildir; aynı
şekilde `RefCell`in çalışma zamanı ödünç sayacı da atomik değildir. Derleyici bu iki tipin
ikisini de `Send` **olarak işaretlemez** — bu, bir eksiklik değil, tam olarak veri
yarışının önlenmesinin kaynağıdır. Bir programcı `Rc<RefCell<PaylasilanKayit>>` değerini
`thread::spawn`e vermeye çalışsaydı, kaynak derlenmezdi; bu redd burada bir hata metniyle
gösterilmiyor, yalnızca nedeni anlatılıyor. Kabul edilen karşılığı zaten yukarıda koşturuldu:
aynı ihtiyaç, sayaçları atomik olan `Arc` ve kilitleme ile koruma sağlayan `Mutex` ile
karşılanıyor. İkisi arasındaki fark yalnız isim değil — `Arc`nin sayacı atomik işlemlerle
güncellenir, `Mutex` da erişimi sırayla verir; ikisi birlikte, `Rc<RefCell<..>>`in tek iş
parçacıklı dünyada üstlendiği rolü, birden fazla iş parçacığının bulunduğu bir dünyada
üstleniyor.

Bu ayrım, tek başına `Mutex` kullanmadan da fark ediliyor. Bir tipin yalnızca `RefCell`
taşıması (paylaşımlı sahiplik olmadan) onu `Sync` olmaktan çıkarır ama `Send` olmaktan
çıkarmaz — bir `RefCell<T>` başka bir iş parçacığına tek başına taşınabilir, çünkü o iş
parçacığı taşındıktan sonra tek sahip olur; ama iki iş parçacığı arasında **paylaşılan bir
referansla** kullanılamaz, çünkü çalışma zamanı ödünç sayacı korumasız kalır. `Rc<T>` ise
ikisini de kaybeder, çünkü `Rc`nin var oluş nedeni zaten paylaşılan sahipliktir ve o
paylaşım iş parçacıkları arasında hiçbir zaman güvenli değildir. Bu üç örnek — sade
`RefCell`, sade `Rc` ve ikisinin birleşimi — aynı kökten gelen üç farklı redde işaret
ediyor: iç değişebilirlik, referans sayımı, ya da ikisinin toplamı; hangisi olursa olsun
kaynaktaki koşul aynı, atomik olmayan bir sayaç ya da atomik olmayan bir denetim
mekanizması.

Bu redd derleme hatasıyla gösterilmiyor olsa da, sonucu Trait'ler, Genellikler ve Hata
Yönetimi kursunda kurulan bir kalıba benziyor: orada da bir tipin belirli bir sınırı
sağlamadığı durumlar kod bloğuna konmadan, yalnızca gerekçesiyle anlatılmıştı. Buradaki
fark, orada ölçülen sınırların genellik (bir tip parametresinin hangi trait'leri
sağlaması gerektiği) üzerine olması, burada ölçülenin ise iş parçacığı sınırını geçme
izni olması — ikisi de derleyicinin bir tipin ne yapabileceğine dair kararını, kodu hiç
çalıştırmadan verdiği örnekler.

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

Tipin bir paylaşıma izin vermesi, o paylaşımı kullanan programın **doğru** olduğu anlamına
gelmiyor. `Arc<Mutex<i32>>` veri yarışını engeller — aynı anda iki iş parçacığı aynı tam
sayıyı değiştiremez — ama hangi işlemin hangi sırayla uygulandığı hâlâ programın kendi
tasarımına bağlıdır. Aşağıdaki ölçüm bunu doğrudan gösteriyor: iki iş parçacığının her biri
paylaşılan bir sayıyı bağımsızca değiştirdiğini varsayarsa beklenen toplam bir değerdir; ama
aynı sayının üzerinde sırayla çalıştıklarında sonuç başka bir değer olur.

Bu, kilitli bir kaynağı paylaşan iki iş parçacığının karşılaşabileceği sorunlardan yalnızca
biri; bir başkası, iki iş parçacığının birbirinin tuttuğu kilidi beklemesi durumunda ortaya
çıkabilir ve bu kurs böyle bir durumu hiç koşturmuyor, çünkü sonucu bir kilitlenmedir ve
belirlenimci bir çıktı üretmez. Burada ölçülen daha sınırlı ve daha temel bir soru: kilidin
kendisi doğru çalışsa bile — hiçbir kilitlenme olmasa, hiçbir veri yarışı olmasa bile —
paylaşılan bir değer üzerinde çalışan iki işlemin sırası, o işlemlerin sonucunu değiştirir
mi. Yanıt evet ve bu, tipin verdiği güvencenin sınırını çiziyor.

```rust
// sinirlayici.rs — tip paylasima izin verir ama sira bagimliligi yanlis sonuc verebilir.

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let baslangic = 5;

    // bagimsiz varsayimla beklenen: A degeri ikiye katlar (10), B kendi payinda 10 ekler (15),
    // ikisi ayri calissaydi toplam 10 + 15 = 25 olurdu.
    let bagimsiz_beklenen = (baslangic * 2) + (baslangic + 10);

    let paylasilan = Arc::new(Mutex::new(baslangic));

    let p1 = Arc::clone(&paylasilan);
    let a = thread::spawn(move || {
        let mut deger = p1.lock().unwrap();
        *deger *= 2;
    });
    a.join().unwrap();

    let p2 = Arc::clone(&paylasilan);
    let b = thread::spawn(move || {
        let mut deger = p2.lock().unwrap();
        *deger += 10;
    });
    b.join().unwrap();

    let gercek_sonuc = *paylasilan.lock().unwrap();

    println!("bagimsiz varsayimla beklenen: {}", bagimsiz_beklenen);
    println!("paylasilan mutex ile gercek sonuc: {}", gercek_sonuc);
    println!(
        "tip paylasima izin verdi, sonuc bagimsiz varsayimla ayni mi: {}",
        gercek_sonuc == bagimsiz_beklenen
    );
}
```

```
bagimsiz varsayimla beklenen: 25
paylasilan mutex ile gercek sonuc: 20
tip paylasima izin verdi, sonuc bagimsiz varsayimla ayni mi: false
```

**IP5.** Beklenen değer 25, iki işlemin birbirinden habersiz, kendi başlangıç değeri
üzerinde çalıştığı varsayımıyla hesaplanıyor: `A` beşi ikiye katlayıp on buluyor, `B` beşe on
ekleyip on beş buluyor, toplam yirmi beş. Gerçek koşumda ise ikisi **aynı** sayıyı
paylaşıyor ve program `A`yı `B`den önce birleştiriyor: `A` beşi ikiye katlayıp ona
çıkarıyor, `B` bu ona on daha ekleyip yirmiye çıkarıyor. Sonuç yirmi, yirmi beş değil — ve bu
fark üç ayrı koşumda birebir aynı çıkıyor, çünkü birleştirme çağrıları sırayı belirlenimci
kılıyor. `Mutex<i32>` tipi bu paylaşıma izin verdi ve veri yarışını da gerçekten engelledi;
engellemediği şey, programcının "bu iki işlem birbirinden bağımsızdır" varsayımının doğru
olup olmadığı. Hangi işlemin **önce** kilit alacağı hâlâ programın tasarımına bağlı bir
sorudur ve rastgele hangisinin kazanacağı bu derste hiç ölçülmüyor — yalnızca sabit bir
sıraya (önce `A`, sonra `B`) zorlanan bu belirli tasarımın sonucu ölçülüyor.

Bu ölçümün asıl öğrettiği, "tip güvenlidir" ile "program doğrudur" iddialarının farklı
şeyler olduğu. `Mutex<i32>` etrafındaki tip sistemi, iki iş parçacığının aynı tam sayıyı
aynı anda değiştirmesini engeller — bu güvence her koşumda geçerlidir ve hiç bozulmaz. Ama
kilidin **hangi sırayla** verileceği, hangi işlemin diğerinden önce çalışacağı, tamamen
programın kendi tasarımına bağlıdır ve tip sistemi bu konuda hiçbir şey söylemez. Muteks ve
Okuma/Yazma Kilidi dersi bu boşluğu doğrudan konu edinecek: bir tarafın kilidi ne zaman
bekleyeceği ve bu beklemenin ilerleme sınıflarından hangisine düştüğü.

## Özet

- `Send` bir tipin sahipliğinin başka bir iş parçacığına taşınabildiğini, `Sync` bir tipe
  referansın başka bir iş parçacığından okunabildiğini söyler; ikisi de derleme zamanında
  denetlenir.
- Alanlarının hepsi `Send`/`Sync` olan bir yapı, `Arc<Mutex<...>>` içine konduğunda sınırı
  geçebiliyor; ölçümde `birinci` kendi başına ilerledi, `ikinci` `birinci`nin birleştirmesi
  tamamlanana kadar bekledi.
- `Rc<RefCell<..>>` sınırı geçemez, çünkü ikisinin de sayacı atomik değildir; kabul edilen
  karşılığı `Arc` (atomik sayaç) ve `Mutex` (sıralı erişim) birlikte sağlıyor.
- Tipin bir paylaşıma izin vermesi veri yarışını engeller, ama programın doğru sonucu
  vereceğini garanti etmez — sıraya bağımlı bir hesap, bağımsız varsayımdan farklı bir
  sonuç üretebilir.
- Hangi işlemin kilidi önce alacağı bu derste ölçülmüyor; yalnızca sabit bir sıraya zorlanan
  belirli bir tasarımın belirlenimci sonucu ölçülüyor.

## Sonraki Adım

Bu derste iki iş parçacığı aynı veriye sırayla erişti, çünkü program birleştirme çağrılarıyla
sırayı kendisi zorladı: `birinci` tamamen bitmeden `ikinci` hiç başlamadı. Ama birleştirme
her zaman uygun bir araç değil — bazen iki iş parçacığının aynı anda çalışıyor olması
gerekiyor ve yalnızca paylaşılan veriye erişim anında sıraya girmesi isteniyor. Bu durumda
bir iş parçacığının diğerinin kilidi bırakmasını beklemesi gerekiyor ve bu bekleme
birleştirmeden farklı bir düzenek. Sıradaki ders bu beklemeyi doğrudan ölçüyor: kilidi
tutan kendi başına ilerlerken, kilidi bekleyen başkası bitince nasıl ilerliyor — ve okuma
amaçlı bir kilidin, yazma amaçlı bir kilitten neden daha gevşek bir kural taşıdığı.
