---
title: 'Atomik İşlemler ve Bellek Sıralaması'
source: 'https://academia.sh/tr/kurslar/rust-eszamanlilik/atomik-islemler-ve-bellek-siralamasi'
course: 'Eşzamanlılık ve Asenkron Rust'
language: tr
updated: '2026-10-21T05:03:45+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Kilitli ve atomik sayaçların aynı toplam artışta birebir aynı sonucu verdiğini koşturarak doğrular'
    - 'Atomik bir sayacın kendi başına ilerlediğini, kilitli bir sayacın bekleyen taraf içerdiğini karşılaştırır'
    - 'Atomik olmanın sıralı olmak anlamına gelmediğini ve hangi artışın önce olduğunun ölçülmediğini gösterir'
---

# Atomik İşlemler ve Bellek Sıralaması

Kilitsiz bir sayaç beklemeden, kimseye ihtiyaç duymadan ilerler; aynı toplam artış hem dışlama kilidiyle hem atomik işlemlerle yapılır ve iki yöntem birebir aynı sonucu verir — fark sonuçta değil, bekleyenin olup olmamasındadır.

Önceki ders bir dışlama kilidini bekleyen tarafın nasıl ilerlediğini ölçtü: kilidi tutan
kendi başına, bekleyen başkası bitince. Bu dersin sorusu şu: her paylaşım bekleyen bir taraf
gerektiriyor mu? Yanıt hayır — belirli bir koşulu sağlayan basit paylaşımlar, hiç kimseyi
beklemeden, donanımın kendi garantisiyle güvenle güncellenebiliyor. Bu, İş Parçacıkları
konusunun son dersi ve kilitsiz ilerlemeyi doğrudan ölçüyor.

Bu koşul dar: yalnızca tek bir makine sözcüğüne sığan basit değerler (tam sayılar,
mantıksal değerler, işaretçi genişliğinde sayılar) atomik olarak güncellenebiliyor. Önceki
derslerde görülen `PaylasilanKayit` gibi birden fazla alan taşıyan bir yapı, ya da
Kanallarla Mesajlaşma dersindeki gibi öbekte büyüyen bir liste, tek bir atomik işlemle
güncellenemez — bu tip veriler hâlâ bir kilide ihtiyaç duyuyor. Bu dersin ölçtüğü kilitsiz
ilerleme, bu yüzden dar ama önemli bir özel durum: tek bir sayının, tek bir bayrağın, tek
bir işaretçinin paylaşılan güncellemesi.

## Donanımın Tek Adımlık Garantisi

İşletim Sistemi Kavramları kursu ve JVM, Eşzamanlılık ve Başarım kursu karşılaştır-ve-değiştir
gibi donanım ilkellerinden söz etti; bu kavram burada yeniden kurulmuyor. Atomik bir işlem,
işlemcinin tek bir bölünmez adımda gerçekleştirdiği bir okuma-değiştirme-yazma dizisidir: bir
sayacı bir arttırmak, iki iş parçacığı aynı anda dener denese de, hiçbir zaman yarım kalmış
ya da üst üste binmiş bir sonuç üretmez. Bir dışlama kilidi de aynı sonucu sağlıyor, ama
farklı bir yolla — kilidi tutan tek taraf hariç herkesi bekletiyor. Atomik işlem ise hiç
kimseyi bekletmiyor; donanımın kendisi, aynı anda gelen iki isteği sıraya koyup ikisini de
kayıpsız işliyor.

Bu güvencenin neden gerekli olduğunu anlamak için, korumasız bir artışın neden yanlış
sonuç verdiğine bakmak yeterli. Bir tam sayıyı bir artırmak aslında üç ayrı adım: mevcut
değeri oku, bir ekle, sonucu geri yaz. İki iş parçacığı bu üç adımı aynı anda, korumasız
biçimde yapmaya çalışırsa, ikisi de aynı eski değeri okuyabilir, ikisi de kendi hesapladığı
yeni değeri yazabilir — ve son yazan, öbürünün artışını **örter**. Sonuç, iki artış
yapılmış olmasına rağmen sayacın yalnızca bir artmış olmasıdır; bu bir veri yarışının
doğrudan sonucu. Atomik bir işlem bu üç adımı donanım düzeyinde tek bir bölünmez birim
hâline getirerek bu örtmeyi imkânsız kılıyor — okuma, ekleme ve yazma arasına hiçbir başka
iş parçacığı giremiyor.

Bu farkın koşumdaki karşılığı şu: bir dışlama kilidiyle korunan bir sayacı arttıran on iş
parçacığından dokuzu, herhangi bir anda dokuzuncusu kilidi tutarken bekliyor olabilir —
başkası bitince sınıfına giriyorlar. Atomik bir sayacı arttıran on iş parçacığının hiçbiri
böyle bir bekleyişe girmiyor; hepsi kendi başına sınıfında kalıyor. Aşağıdaki ölçüm bu
farkı, iki yöntemin ürettiği **sonucu** karşılaştırarak gösteriyor: ikisi de aynı toplamı
üretiyor, çünkü ikisi de kayıp güncelleme olmadan çalışıyor — farklı olan yalnızca bunu nasıl
başardıkları.

İş Parçacıkları Arası Güvenliğin Tipteki Karşılığı dersinde kurulan `Send`/`Sync` çerçevesi
burada da geçerli: `AtomicUsize` tipi hem `Send` hem `Sync`dir, bu yüzden `Arc<AtomicUsize>`
birden fazla iş parçacığı arasında paylaşılabiliyor. Bu, `Arc<Mutex<usize>>`in taşıdığı
aynı iki izinle aynı — aradaki fark, `Mutex`in kendi içinde bir kilit taşıması, `AtomicUsize`
tipinin ise hiçbir kilit taşımadan, doğrudan donanımın bölünmez işlemlerine dayanmasıdır.
İkisi de tip sisteminde aynı iznin bir örneği, ama garantiyi sağlama biçimleri farklı.

## İki Yöntem, Aynı Sonuç

Aşağıdaki program iki işlev tanımlıyor ve `main` içinde ikisini de aynı sabitlerle
çalıştırıyor: on iş parçacığı, her biri beş yüz kez artış. `kilitle_ve_say` bunu bildiğimiz
`Arc<Mutex<usize>>` düzeneğiyle yapıyor — her artış bir tutamaç istiyor, tutamaç kapsam
dışına çıkınca kilit bırakılıyor. `atomik_say` aynı işi `Arc<AtomicUsize>` ile yapıyor;
burada tutamaç yok, yalnızca `fetch_add` adlı tek bir çağrı var ve bu çağrı hem okuma hem
yazma işini kendi içinde, bölünmez biçimde yapıyor. İki işlevin de dönüş tipi aynı
(`usize`), böylece sonuçları doğrudan karşılaştırılabiliyor.

```rust
// atomik.rs — kilitsiz bir sayac beklemeden ilerler; artis sirasi olculmez, yalniz toplam.

use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;

const IS_PARCACIGI: usize = 10;
const ARTIS: usize = 500;

fn kilitle_ve_say() -> usize {
    let sayac = Arc::new(Mutex::new(0usize));
    let mut tutamaclar = Vec::new();
    for _ in 0..IS_PARCACIGI {
        let s = Arc::clone(&sayac);
        tutamaclar.push(thread::spawn(move || {
            for _ in 0..ARTIS {
                // her artis kilidi ister: bekleyen bir is parcacigi baskasi bitince ilerler
                let mut deger = s.lock().unwrap();
                *deger += 1;
            }
        }));
    }
    for t in tutamaclar {
        t.join().unwrap();
    }
    let sonuc = *sayac.lock().unwrap();
    sonuc
}

fn atomik_say() -> usize {
    let sayac = Arc::new(AtomicUsize::new(0));
    let mut tutamaclar = Vec::new();
    for _ in 0..IS_PARCACIGI {
        let s = Arc::clone(&sayac);
        tutamaclar.push(thread::spawn(move || {
            for _ in 0..ARTIS {
                // hicbir is parcacigi kilit beklemez: her artis kendi basina ilerler
                s.fetch_add(1, Ordering::SeqCst);
            }
        }));
    }
    for t in tutamaclar {
        t.join().unwrap();
    }
    sayac.load(Ordering::SeqCst)
}

fn main() {
    let kilitli_sonuc = kilitle_ve_say();
    let atomik_sonuc = atomik_say();
    let beklenen = IS_PARCACIGI * ARTIS;

    println!("beklenen toplam: {}", beklenen);
    println!("kilitle sayilan toplam: {}", kilitli_sonuc);
    println!("atomik sayilan toplam: {}", atomik_sonuc);
    println!(
        "iki yontem ayni sonucu verdi: {}",
        kilitli_sonuc == atomik_sonuc && atomik_sonuc == beklenen
    );

    // --- iki atomik sayac: hangi artisin once oldugu olculmuyor, yalniz nihai toplam ---
    let a = Arc::new(AtomicUsize::new(0));
    let b = Arc::new(AtomicUsize::new(0));
    let mut tutamaclar = Vec::new();
    for i in 0..IS_PARCACIGI {
        let (a, b) = (Arc::clone(&a), Arc::clone(&b));
        tutamaclar.push(thread::spawn(move || {
            if i % 2 == 0 {
                a.fetch_add(1, Ordering::SeqCst);
            } else {
                b.fetch_add(1, Ordering::SeqCst);
            }
        }));
    }
    for t in tutamaclar {
        t.join().unwrap();
    }
    let toplam_ikili = a.load(Ordering::SeqCst) + b.load(Ordering::SeqCst);
    println!();
    println!("iki ayri atomik sayacin toplami: {}", toplam_ikili);
    println!("(hangi is parcaciginin hangi sayaci once arttirdigi basilmiyor)");
}
```

```
beklenen toplam: 5000
kilitle sayilan toplam: 5000
atomik sayilan toplam: 5000
iki yontem ayni sonucu verdi: true

iki ayri atomik sayacin toplami: 10
(hangi is parcaciginin hangi sayaci once arttirdigi basilmiyor)
```

**IP8.** On iş parçacığı, her biri beş yüz kez artırarak, aynı sayacı beş bin defa
arttırıyor. `kilitle_ve_say` bunu bir dışlama kilidiyle yapıyor: her artış `lock` çağrısı
istiyor ve on iş parçacığından dokuzu herhangi bir anda kilit için bekleyebiliyor —
başkası bitince sınıfına giren bir düzenek. `atomik_say` aynı beş bin artışı `fetch_add`
çağrısıyla yapıyor: hiçbir iş parçacığı bir kilide ihtiyaç duymuyor, hepsi kendi başına
sınıfında kalıyor. İki yöntem de **beklenen** toplamı (5000) üretiyor ve bu sonuç üç ayrı
koşumda birebir aynı çıkıyor — kilitli ve atomik sayaçlar arasında hiçbir kayıp güncelleme
farkı yok.

İki işlevin yapısı da bunu görünür kılıyor: `kilitle_ve_say` içindeki döngü gövdesi bir
`Mutex` tutamacı alıyor, değeri değiştiriyor ve tutamacı bırakıyor — bu üç adım her artışta
tekrarlanıyor. `atomik_say` içindeki döngü gövdesi ise yalnızca `fetch_add` çağırıyor; ne
bir tutamaç alınıyor ne de açıkça bırakılıyor, çünkü `AtomicUsize` tipinin kendisi bir kilit
taşımıyor. İki işlevin de aynı sayıda iş parçacığı, aynı sayıda artış kullanması ve aynı
`beklenen` sabitle karşılaştırılması, tek değişkenin **yöntem** olmasını sağlıyor — sonuçtaki
her fark, kilitli olup olmamaktan kaynaklanıyor, başka hiçbir değişkenden değil.

## Sıralama Ölçülmez, Yalnızca Toplam

Programın ikinci bölümü iki ayrı atomik sayaç kullanıyor: çift numaralı iş parçacıkları `a`
sayacını, tek numaralı iş parçacıkları `b` sayacını artırıyor. On iş parçacığının hangi
sırayla çalıştığı, hangisinin önce `fetch_add` çağırdığı bu programda hiçbir yerde
ölçülmüyor ve basılmıyor — yalnızca iki sayacın nihai toplamı (`10`) yazdırılıyor. Bu, İş
Parçacığı Oluşturma dersinde kurulan kuralın burada da geçerli olduğunu gösteriyor:
zamanlamaya bağlı hiçbir gözlem basılmıyor, yalnızca belirlenimci olan — bu örnekte, kaç
iş parçacığının çift ya da tek numaralı olduğu, dolayısıyla nihai toplamın ne olacağı —
basılıyor.

Bu ikinci ölçümün amacı, tek bir sayaç yerine iki ayrı sayaç kullanarak, "hangi iş
parçacığı önce çalıştı" sorusunun cevabının programın **çıktısını değiştirmediğini**
göstermek. On iş parçacığının zamanlayıcı tarafından hangi sırayla çalıştırıldığı her
koşumda farklı olabilir — bu programda `IS_PARCACIGI` sabiti on olduğu için beş tanesi `a`
sayacına, beş tanesi `b` sayacına yazıyor ve iki sayacın toplamı her zaman on. Zamanlama
değişse bile bu toplam değişmiyor, çünkü `i % 2` hesabı iş parçacığının **numarasına**
bağlı, çalıştırılma **sırasına** değil. Bu, bir hesabın sonucu ile o hesabı üreten
işlemlerin sırası arasındaki farkı gösteren bir başka örnek.

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

Atomik olmak sıralı olmak anlamına gelmiyor. Bu derste kullanılan `Ordering::SeqCst`,
işlemin kendisinin bölünmez olduğunu garanti ediyor — hiçbir iş parçacığı yarım kalmış bir
artışı göremiyor — ama **hangi** artışın diğerinden önce gerçekleştiğini bu koşumda hiçbir
yerde belirlemiyoruz ve belirleyemeyiz de: on iş parçacığı aynı anda çalışıyor ve
zamanlayıcının onlara işlemci payını hangi sırayla vereceği ortama bağlı. Ölçülen tek şey,
beş bin artışın **hiçbirinin kaybolmadığı** — final toplamın tam olarak beklenen sayıya
eşit olduğu. Hangi iş parçacığının hangi anda kaçıncı artışı yaptığı sorusu, bu kursun
belirlenimcilik kuralı gereği hiçbir yerde ölçülmüyor.

Bellek sıralamasının kendisi — `Ordering::SeqCst`, `Ordering::Relaxed` ve aradaki
seçenekler — bu derste **kavram olarak** anlatılıyor, ölçülmüyor, çünkü ölçülemiyor: bir
sıralama seçeneğinin diğerinden farkı, çok işlemcili bir donanımın kendi önbellek
tutarlılık davranışında ortaya çıkıyor ve bu davranış koşumdan koşuma, hatta aynı koşum
içinde çağrıdan çağrıya değişebiliyor. `SeqCst` en güçlü ve en kolay akıl yürütülen
seçenektir: bütün iş parçacıkları, bütün atomik işlemleri aynı global sırada görüyormuş gibi
davranır. Daha gevşek seçenekler (`Relaxed` gibi) yalnızca tek bir değişkenin bölünmezliğini
garanti eder, işlemler arasındaki görünürlük sırasını garanti etmez — bu fark, iki farklı
atomik değişken arasında bir ilişki kurulmak istendiğinde önem kazanır, ama bu derste
kullanılan tek bir sayaç senaryosunda görünmez.

Neden `SeqCst` seçildiği de bu sınırın bir parçası. Bu ders tek bir atomik değişkeni (ve
ikinci ölçümde, birbirinden bağımsız iki atomik değişkeni) arttırıyor; iki farklı atomik
değişken arasında bir okumanın diğerinin yazmasından **önce mi sonra mı** göründüğünü
gözlemleyen bir senaryo burada kurulmuyor. Böyle bir senaryo kurulsaydı, seçilen sıralama
seçeneği sonucu etkileyebilirdi ve o fark yalnızca belirli işlemci mimarilerinde, belirli
önbellek durumlarında ortaya çıkardı — yani ortama bağlı bir gözlem olurdu. Bu kursun
kuralı gereği böyle bir gözlem hiç kurulmuyor; `SeqCst` en güvenli seçenek olduğu için
seçildi ve dersin ölçtüğü tek şey — toplamın doğruluğu — açısından hiçbir sıralama
seçeneği bir diğerinden farklı sonuç vermezdi.

## Özet

- Atomik bir işlem, donanımın tek bir bölünmez adımda gerçekleştirdiği bir okuma-değiştirme-
  yazma dizisidir; kilitsiz bir sayaç bu sayede kimseye ihtiyaç duymadan, kendi başına
  ilerler.
- Aynı beş bin artış hem kilitli hem atomik yöntemle yapıldığında birebir aynı toplamı
  (5000) üretiyor; fark sonuçta değil, kilitli sayaçta bekleyen bir tarafın bulunması,
  atomik sayaçta ise hiç bulunmamasında.
- Kilitli bir sayaçta iş parçacıkları kilit için bekleyerek başkası bitince sınıfına
  girebilir; atomik bir sayaçta hiçbir iş parçacığı kimseyi beklemez, hepsi kendi başına
  ilerler.
- Atomik olmak sıralı olmak değildir: hangi artışın önce gerçekleştiği bu kursta hiç
  ölçülmez ve basılmaz; ölçülen yalnızca nihai toplamın doğruluğudur.
- Bellek sıralaması seçenekleri kavram olarak anlatılır, koşumla ölçülmez, çünkü
  aralarındaki fark donanımın önbellek davranışına bağlıdır ve bu davranış basılmaz.
- Kilitsiz ilerleme dar bir koşula bağlıdır: yalnızca tek bir makine sözcüğüne sığan basit
  değerler atomik olarak güncellenebilir; birden fazla alan taşıyan bir yapı hâlâ bir
  kilide ihtiyaç duyar.

## Sonraki Adım

Bu konunun beş dersi boyunca ölçülen birimler kursun dört sınıfından üçüne düştü: kendi
başına ilerleyen bağımsız iş parçacıkları — bu derste görülen kilitsiz sayaçlar da bu sınıfta
—, başkasının bitmesini bekleyen taraflar ve kaynakta yazılı olduğu hâlde hiç ilerlemeyen
birimler. İlerleyenlerin hepsi ortak bir varsayıma dayanıyordu: bir iş parçacığı,
başlatıldığı andan itibaren işletim sisteminin zamanlayıcısı tarafından ileriye taşınıyor —
programcı yalnızca ne zaman **bekleyeceğini** belirliyor, ilerlemenin kendisini değil.
Dördüncü sınıf, `yoklanınca`, bu konuda hiç görülmedi.

Eşzamansız Programlama konusu bu varsayımı tersine çeviriyor. Kaynakta yazılan bir görev,
hiçbir iş parçacığına bağlanmadan, kendiliğinden hiç ilerlemeyen tembel bir değer olarak
duruyor — bir iş parçacığının aksine, bir görev başlatıldığı anda hiçbir şey yapmıyor.
Sıradaki ders bu farkı doğrudan ölçüyor: aynı kaynaktan kurulan iki görevden biri bir
yürütücüye verilir ve ilerler, öbürü verilmez ve hiçbir kayıt üretmez — kaynakları birebir
aynı olsa da.
