---
title: 'POSIX İş Parçacıkları'
source: 'https://academia.sh/tr/kurslar/c-sistem-programlama/posix-is-parcaciklari'
course: 'C ile Sistem Programlama'
language: tr
updated: '2026-09-26T03:02:45+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'kilit altında iki iş parçacığının aynı sayacı artırdığını, sonucun tam ve deterministik olduğunu ölçerek gösterir'
    - 'kilidin garanti ettiği şeyin görünürlük olduğunu, sıralama olmadığını ayırt eder'
    - "iş parçacıklarının bellek uzayını fork'un aksine paylaştığını, önceki derslerdeki sınıflarla karşılaştırarak açıklar"
---

# POSIX İş Parçacıkları

Ayni surec icindeki iki is parciciginin ayni bellegi kilit altinda degistirmesi, borunun tasidigi baytlar gibi bir kez tuketilen bir aktarim degil, surekli paylasilan bir ortak degisimdir.

Sistem arayüzlerinin son dersi aynı süreç içinde kalıyor, ama süreç sınırını aşan bir taşıma
biçiminden vazgeçip aynı bellek uzayını paylaşan iki akışa geçiyor: **iş parçacığı**.
`fork` bir kopya verdi, sinyal bir aktarım bıraktı, boru ve `mmap` süreçler arası
sınırı iki farklı biçimde geçti. İki iş parçacığı ise hiç sınır geçmiyor — aynı
sürecin aynı belleğini baştan paylaşıyor. Bir kilit altında yapılan bir değişiklik bu
yüzden kursun dördüncü sınıfına düşüyor: **ortak**.

`pthread_create` bunu nasıl kurduğuyla da `fork`tan ayrılıyor. `fork` çağrıldığı anda
çekirdek belleğin sayfa tablosunu kopyalıyor — kopyala-yaz (`copy-on-write`) sayesinde
fiziksel sayfalar hemen çoğaltılmıyor, ama iki süreç artık ayrı adres uzaylarına
sahip, biri yazınca öteki görmüyor. `pthread_create` ise yeni bir adres uzayı açmıyor;
yalnız yeni bir yığıt (`stack`) ayırıp aynı sürecin içinde ikinci bir yürütme noktası
başlatıyor. Kod bölütü, veri bölütü, öbek (`heap`) — hepsi tek bir kopya, iki akış
aynı adresleri görüyor. Bu yüzden `sayac` gibi bir küresel değişken iki iş parçacığı
için de aynı bellek konumu; `fork` sonrası aynı isimli bir değişkenin iki bağımsız
kopyası olmasıyla karşılaştırıldığında, bu fark bu dersin sınıfını doğrudan belirliyor.

## Kilit Altında Yazılan Sayaç

`gozlem.h` ve `gozlem.c` değişmiyor. Bu dersin kendi dosyası `parcacik_olcumu.c`, iki
iş parçacığının aynı sayacı kilit altında artırdığı ve sonucun tam olarak beklenen
değere ulaştığı tek bir ölçümü koşturuyor.

```c
// gozlem.h — bir degisikligin kime ulastigini siniflayan kayit
#ifndef GOZLEM_H
#define GOZLEM_H

enum Sinif { KOPYA, ORTAK, AKTARIM, GOZLENMEYEN };

/* Bir degisikligi kaydeder: gordu, degisikligi yapmayan tarafin onu
   okuyup okuyamadigidir (1 ya da 0). */
void gozlem(const char *degisiklik, enum Sinif sinif, int gordu);

/* Sinif basina sayilari ve ulasan / ulasmayan toplamini basar. */
void gozlem_ozeti(void);

#endif
```

```c
// gozlem.c — sayilar yalniz bu birimde tutulur
#include <stdio.h>
#include "gozlem.h"

static const char *const adlar[] = { "kopya", "ortak", "aktarim", "gozlenmeyen" };
static int sayilar[4];
static int ulasan = 0;
static int ulasmayan = 0;

void gozlem(const char *degisiklik, enum Sinif sinif, int gordu) {
    /* ortak ve aktarim ulasir, kopya ve gozlenmeyen ulasmaz */
    int beklenen = (sinif == ORTAK || sinif == AKTARIM);
    sayilar[sinif]++;
    if (gordu) {
        ulasan++;
    } else {
        ulasmayan++;
    }
    printf("%-34s %-8s %s%s\n", degisiklik, gordu ? "gordu" : "gormedi",
           adlar[sinif], gordu == beklenen ? "" : "  TUTARSIZ");
}

void gozlem_ozeti(void) {
    printf("siniflar: kopya %d, ortak %d, aktarim %d, gozlenmeyen %d\n",
           sayilar[KOPYA], sayilar[ORTAK], sayilar[AKTARIM], sayilar[GOZLENMEYEN]);
    printf("ulasan %d, ulasmayan %d\n", ulasan, ulasmayan);
}
```

```c
// parcacik_olcumu.c — iki is parciciginin kilit altinda ayni sayaci artirmasi
#define _POSIX_C_SOURCE 200809L
#include <pthread.h>
#include <stdio.h>
#include "gozlem.h"

#define ADIM 1000

static long sayac = 0;
static pthread_mutex_t kilit = PTHREAD_MUTEX_INITIALIZER;

static void *artir(void *veri) {
    (void)veri;
    for (int i = 0; i < ADIM; i++) {
        pthread_mutex_lock(&kilit);
        sayac = sayac + 1;
        pthread_mutex_unlock(&kilit);
    }
    return NULL;
}

int main(void) {
    pthread_t p1, p2;

    pthread_create(&p1, NULL, artir, NULL);
    pthread_create(&p2, NULL, artir, NULL);

    pthread_join(p1, NULL);
    pthread_join(p2, NULL);

    gozlem("iki is parciciginin kilit altinda yazdigi toplam", ORTAK,
           sayac == 2L * ADIM);

    pthread_mutex_destroy(&kilit);
    gozlem_ozeti();
    return 0;
}
```

```text
iki is parciciginin kilit altinda yazdigi toplam gordu    ortak
siniflar: kopya 0, ortak 1, aktarim 0, gozlenmeyen 0
ulasan 1, ulasmayan 0
```

**SA16.** Tek satır `gordu` diyor, ve bu sonuç şans eseri değil, garanti edilmiş. İki
iş parçacığı da `artir` işlevini koşturuyor, her biri `sayac`ı bin kez artırıyor;
ana iş parçacığı her iki `pthread_join` çağrısı döndükten sonra `sayac`a bakıyor ve
tam olarak `2000` buluyor. Bu, `fork`un verdiği bağımsız kopyalardan temelde farklı:
`pthread_create` yeni bir bellek kopyası açmıyor, aynı sürecin aynı adres uzayında
yeni bir yürütme akışı başlatıyor. `sayac` değişkeni tek bir yerde duruyor, ve iki
akış da o tek yere yazıyor — bu yüzden sınıf **ortak**, `fork`taki **kopya** değil.
`ADIM` sabiti bin olarak seçildiği için toplam da kesin: iki iş parçacığı toplam
`2 * 1000 = 2000` artırma yapıyor, ve kilit hiçbirini kaybettirmiyorsa sonuç yalnız
bu sayı olabilir — ara sıra `1998` ya da `2001` çıkan bir ölçüm bu dersin kabul
ettiği bir sonuç değil, çünkü kursun ölçüm yöntemi yalnız deterministik, tekrar
tekrar aynı çıkan sonuçları doğruluyor.

`sayac = sayac + 1` satırı tek bir C ifadesi gibi görünse de üç ayrı adıma karşılık
gelir: belleğe git, değeri oku, bir ekle, sonucu geri belleğe yaz. Kilit olmadan bu
üç adım kesintiye açıktır — bir iş parçacığı okuma ile yazma arasındayken öteki
iş parçacığı da aynı eski değeri okuyabilir, ikisi de kendi hesapladığı `+1`i geri
yazar ve iki artırımdan biri kaybolur. `pthread_mutex_lock` bu üç adımı bölünmez
bir birim hâline getirir: kilidi tutan iş parçacığı üçünü de tamamlamadan öteki
iş parçacığı kilidi alamaz, bu yüzden hiçbir artırım kayıp gitmez. `ADIM` değerinin
büyüklüğü de kasıtlı — yeterince küçük bir döngü sayısında kilitsiz bir sürüm bile
şans eseri doğru sonuç verebilir, çünkü iki iş parçacığının adımları çakışmadan
sırayla ilerleyebilir; bin adımlık bir döngü bu şansı pratikte sıfıra indiriyor,
ama kursun metni bunu hiç koşturmadan, yalnız düzyazıyla anlatıyor.

Java'nın standart koleksiyonlarında bu sorunun farklı bir yüzü var: bir `ArrayList`
ya da `HashMap` üzerinde gezinirken (`iterator`) koleksiyon başka bir iş parçacığınca
değiştirilirse, çoğu gerçekleştirim veriyi sessizce bozmak yerine
`ConcurrentModificationException` fırlatır — bu "hızlı-başarısız" (`fail-fast`)
davranış, M08/K03'ün "Yineleyiciler ve Eşzamanlı Değişiklik" konusunun kapsamı.
`parcacik_olcumu.c`'deki `sayac` ise tekil bir `long`, gezinilen bir yapı değil;
kilit onu bozulmadan tutuyor, ama C'nin kendisinde Java'nınkine benzer bir çalışma
zamanı denetimi yok — kilidi unutup `sayac`a doğrudan yazan bir kod, hatasız
derlenir ve sessizce yanlış sonuç üretir, `ConcurrentModificationException` gibi bir
uyarı vermez. Denetim burada derleyicide ya da çalışma zamanında değil, tamamen
programcının disiplininde.

## Kilidin Garanti Ettiği: Görünürlük, Sıralama Değil

**SA17.** `pthread_mutex_lock` ve `pthread_mutex_unlock` çifti `sayac = sayac + 1` satırını
sarıyor, ve bu sarmalama iki ayrı şeyi birden garanti ediyor. Birincisi **karşılıklı
dışlama**: iki iş parçacığı aynı anda kilidi tutamaz, bu yüzden `sayac`ı okuyup bir
artırıp geri yazma adımı hiçbir zaman yarım kesilmez — biri bu üç adımı tamamlamadan
öteki başlayamaz. İkincisi **görünürlük**: bir iş parçacığı kilidi bıraktığında,
yaptığı değişiklik kilidi bir sonra alan iş parçacığına garantili olarak görünür.
Kilit olmadan bu iki garanti de kaybolur — iki iş parçacığı aynı `sayac`ı aynı anda
okuyup aynı eski değere bir ekleyebilir, ve bir artırma kaybolabilir. `parcacik_olcumu.c`
bu kayıp durumu hiç koşturmuyor; kilitsiz bir sürüm tarif ediliyor ama çalıştırılmıyor,
çünkü sonucu deterministik değil, ve bu kursun ölçüm yöntemi yalnız deterministik
sonuçları doğruluyor.

Kilit görünürlüğü garanti ederken **hangi iş parçacığının önce çalıştığını** garanti
etmiyor. `p1` ve `p2` hangi sırayla zamanlanırsa zamanlansın, hangisinin kilidi kaç
kez önce aldığı `parcacik_olcumu.c` içinde hiç ölçülmüyor ve hiç basılmıyor — yalnız
nihai toplam ölçülüyor. Bu, kursun baştan koyduğu kurala da uyuyor: iş parçacığı
kimliği hiçbir zaman yazdırılmıyor, ve iki akış da kendi aralarında yazdırma yapmıyor,
yalnız ana iş parçacığı her iki `join` döndükten sonra tek bir satır basıyor.

Java Bellek Modeli'nde (JMM) benzer bir görünürlük garantisi `volatile` anahtar
sözcüğüyle ve senkronize bloklarla kuruluyor — M08/K04'ün "JVM, Eşzamanlılık ve
Başarım" konusu bunu "önce-olur" (`happens-before`) ilişkisiyle tanımlıyor: bir
kilidi bırakan iş parçacığının o kilitten önceki tüm yazmaları, kilidi bir sonra
alan iş parçacığına görünür olur. Kural kelime kelime `pthread_mutex_unlock`/
`pthread_mutex_lock` çiftinin verdiği garantiyle örtüşüyor; fark, JMM'nin bunu
sanal makinenin bellek sıralaması üzerinden **dilin kendi belirtiminde** tanımlamış
olması, C'de ise bu garantinin POSIX iş parçacığı belirtiminin çağrı sözleşmesinden
geldiği — derleyici ve donanım sıralamasını `pthread_mutex_lock`/`unlock`
çağrılarının kendisi kısıtlıyor, dilin bir bellek modeli olarak ayrı bir kavramı yok.

Go'nun eşzamanlılık modeli aynı sorunu tersten çözüyor. M09/K03'ün "Goroutine ve
Kanallar" konusundaki ilke — "iletişimle bellek paylaş, belleği paylaşarak iletişim
kurma" — bir değeri bir kanaldan geçirmeyi, o değerin sahipliğini göndericiden
alıcıya devretmek olarak tanımlıyor; iki goroutine aynı değişkene aynı anda
yazmıyor, değer bir kanal üzerinden bir kez akıyor. `parcacik_olcumu.c`'deki
`sayac` ise tam tersi: sahiplik hiç devredilmiyor, iki iş parçacığı da aynı
konuma aynı anda erişme hakkına sahip ve bu hakkı `pthread_mutex_t` ile
sınırlıyor. Go'da kanal deseni seçilseydi `sayac`ı paylaşmak yerine her
goroutine kendi kısmi toplamını hesaplayıp bir kanaldan gönderir, toplama
adımı tek bir alıcıda yapılırdı — bu kursun ölçtüğü **ortak** sınıfı değil,
`boru`nun taşıdığına benzer bir **aktarım** deseni olurdu.

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

**SA18.** Bu ölçüm yalnız **nihai değeri** doğruluyor, ara durumu değil. `sayac`ın kilit
altındayken hangi ara değerlerden geçtiği — `1`, `2`, `3` sırasıyla mi yoksa iki iş
parçacığının adımları nasıl örülerek mi ilerlediği — hiçbir yerde ölçülmüyor, çünkü
ölçmenin kendisi ek bir kilitli okuma gerektirir ve bu da ölçülen sistemi değiştirir.
`parcacik_olcumu.c`'nin doğruladığı tek şey, `2000` adımın hiçbirinin kaybolmadığı;
hangi adımın hangi iş parçacığından geldiği sorusu bu dersin kapsamı dışında. Bu sınır
kasıtlı: kurs, iki akışın birbirini görüp görmediğini ölçüyor, aralarındaki yarışı
yeniden kurmuyor. Bir yarış durumunun kendisini gözlemlemek isteyen bir ölçüm —
örneğin hangi iş parçacığının kilidi kaçıncı sırada aldığını saymak — zamanlamaya
bağımlı hâle gelir ve bu kursun deterministik-sonuç kuralını bozar; bu yüzden böyle
bir ölçüm burada yer almıyor.

Bu dersin en büyük sınırı, kilidin **doğru kullanıldığını varsaymasında**. `kilit`
değişkeni `artir` içinde her erişimden önce alınıyor, ama bunu zorlayan hiçbir
mekanizma yok — `sayac`ı kilitsiz okuyan bir üçüncü işlev eklense, derleyici hiçbir
uyarı vermez, kod sessizce hatalı olur. Rust'ın eşzamanlılık modeli bu boşluğu tip
sistemine taşıyor: M10/K04'ün "`Send` ve `Sync`" konusu, bir tipin iş parçacıkları
arasında güvenle taşınıp taşınamayacağını (`Send`) ve paylaşılan referans olarak
güvenle kullanılıp kullanılamayacağını (`Sync`) derleme zamanında işaretliyor.
Paylaşılan bir sayaç Rust'ta çıplak bir değişken olarak değil, `Mutex<i64>` gibi bir
sarmalayıcı içinde tutulur, ve bu sarmalayıcıya kilit almadan erişmeye çalışan kod
**derlenmez** — `parcacik_olcumu.c`'deki gibi bir hatayı çalışma zamanına değil
derleme zamanına taşır. C'de böyle bir denetim yok: `pthread_mutex_t kilit`
`sayac`la aynı dosyada duruyor olsa bile, aralarındaki bağ yalnız bu dersin metninde
anlatılan bir kural, derleyicinin bildiği bir kısıt değil. Bu kursun ölçüm yöntemi
de zaten bu boşluğu kapatmıyor — yalnız kilidin doğru kullanıldığı tek bir koşuyu
doğruluyor, kilidin unutulduğu bir sürümü hiç koşturup göstermiyor.

## Sonraki Adım ve Sınıfların Toplu Görünümü

**SA19.** Bu beş ders boyunca aynı dört sınıf farklı taşıyıcılarla karşımıza çıktı.
`fork` sonrası bir değişken **kopya**ydı — iki taraf bağımsız, hiçbir değişiklik
karşı tarafa ulaşmadı. Bir sinyal işleyicisinin bıraktığı bayrak **aktarım**dı —
ana akışa bir kez ulaştı. Bir borunun taşıdığı bayt da **aktarım**dı, ikinci okuma
dosya sonuyla bitti. `MAP_SHARED` ile kurulan bir eşlem ve bu dersin kilitli sayacı
ise **ortak**tı — bir kayıt sürekli paylaşılıyor, taraflardan biri değiştirdiğinde
öteki de görüyor. Dördüncü sınıf, **gözlenmeyen**, ilk dersin kendi kapsamında
kaldı: gözlemin kendisinin görülmediği durumlar.

Aynı sonucu farklı bir düzenekle üretmek de mümkündü — örneğin `sayac`ı kilit yerine
atomik bir işlemle artırmak, ya da iş parçacığı yerine paylaşılan bellek üzerinde
başka bir eşzamanlama aracı kullanmak. Bu dersin sınırı, yalnız `pthread_mutex_t` ile
kurulan en yaygın deseni ölçmesi; atomik işlemler ve öteki kilit türleri bu kursun
kapsamı dışında kalıyor.

Bu dört sınıf, taşıyıcının kendisinden bağımsız bir soruyu yanıtlıyor: bir değişiklik
yapan taraf dışındaki bir taraf, o değişikliği görebiliyor mu, ve görüyorsa ne zaman,
kaç kez? `fork` sorusuna hayır diyor — kopya, göstermez. Sinyal ve boru evet diyor
ama yalnız bir kez — aktarım, tüketilir. `MAP_SHARED` ve kilitli sayaç evet diyor ve
sürekli — ortak, tüketilmez. İlk dersin gözlem düzeneğinin kendisi ise bu sorunun
dördüncü, çürük bir yanıtı: bir taraf bir şey görüyor ama ölçüm bunu hiç yakalamıyor —
gözlenmeyen. Beş ders boyunca kullanılan `gozlem()` işlevi bu dört yanıtı tek bir
kelimeyle etiketleyip, o etiketin ölçülen `gordu`/`gormedi` değeriyle tutarlı olup
olmadığını kendiliğinden denetledi; hiçbir ders bu denetimi atlamadı ve hiçbir ölçüm
`TUTARSIZ` etiketiyle bitmedi.

Bu sınıflandırma taşıyıcıya değil **görünürlük deseni**ne bakıyor olduğu için, başka
dillerdeki eşzamanlılık araçlarına da uygulanabilir — M08/K04'teki JMM, M09/K03'teki
kanal, M10/K04'teki `Send`/`Sync` hepsi aynı dört soruyu kendi sözdizimleriyle
yanıtlıyor, yalnız garantiyi nerede kurduğu (çalışma zamanı, tip sistemi, dil
belirtimi) değişiyor. C'nin POSIX iş parçacığı ve süreç arayüzü bu garantiyi hep
çağrı sözleşmesinde kuruyor: `fork` çağrısının kendisi kopya sınıfını, `pthread_
mutex_lock`/`unlock` çifti ortak sınıfını, sinyal teslimi ve boru okuması aktarım
sınıfını tanımlıyor. Sistem arayüzleri burada kapanıyor: beş ders, bir değişikliğin bir
sürecin ya da bir iş parçacığının dışına kimin görebileceği sorusunu dört sınıfla
yanıtladı.

## Özet

- İki iş parçacığı aynı süreç içinde aynı bellek uzayını paylaşır; `fork`un aksine
  ayrı bir kopya açılmaz.
- Bu dersin sınıfı **ortak**: kilit altında yapılan bir değişiklik her iki tarafa
  da görünür, bir kayıt gibi sürekli paylaşılır.
- `pthread_mutex_lock`/`unlock` iki garanti verir: karşılıklı dışlama ve görünürlük.
  Hangi iş parçacığının önce çalıştığını garanti etmez.
- Ölçüm yalnız nihai toplamı doğruluyor; ara durumları ya da iş parçacıkları
  arasındaki sıralamayı ölçmüyor, çünkü bu ölçmenin kendisi zamanlamaya bağımlı
  hâle gelirdi.
- Kilitsiz bir sürüm yalnız düzyazıyla tarif ediliyor, hiç koşturulmuyor; sonucu
  deterministik olmadığı için bu kursun ölçüm yöntemine uymuyor.
- Aynı görünürlük sorusu başka dillerde farklı yerlerde çözülüyor: Java'da JMM'nin
  `happens-before` ilişkisinde, Go'da kanal sahiplik devrinde, Rust'ta `Send`/`Sync`
  tipinde — C'de ise yalnız `pthread_mutex_t` çağrı sözleşmesinde, derleyici desteği
  olmadan.

## Sonraki Adım

Sistem arayüzleri burada tamamlanıyor. Beş ders boyunca aynı `gozlem()` birimi beş farklı
taşıyıcıyı — kopyalanan bellek, işleyicinin bıraktığı bayrak, boru, paylaşılan sayfa,
kilitli sayaç — aynı dört sınıfla ölçtü; hiçbiri diğerinin yerine geçmedi, çünkü her
biri farklı bir soruyu, "kim ne zaman neyi görüyor" sorusunun farklı bir biçimini
yanıtladı. Bir sonraki ders (Bağlı Yapılar) aynı gözlem birimini süreç ve iş
parçacığı sınırından alıp tek bir veri yapısının içine taşıyor: iki işaretçi aynı
düğümü gösterdiğinde, birinin yaptığı değişikliği öteki görüyor mu?
