Singleflight Pattern: C'de Tekrarlanan İşleri Engelleme

Veritabanından veri çeken, API'ye bağlanan ya da ağır hesaplama yapan bir fonksiyonu düşün. Eğer aynı anda 100 kullanıcı aynı veriyi isterse, fonksiyon 100 kez çalışır ve sistem yavaşlar. Singleflight pattern bu sorunu çözer: ilk istek işlemi başlatır, diğerleri sonucu bekler ve hepsi aynı cevabı alır.

C'de bu deseni uygulamak için mutex ve condition variable kullanırız. İşin durumunu takip eden bir yapı oluştururuz; başarılı sonuç ya da hata varsa depoluyoruz, bekleyenlere bildiririz.

pratik bir örnek:

Reklam · TEST
AdSense Test ReklamıYazı arası (gönderi içi) · Yazı arası
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <pthread.h>
5#include <unistd.h>
6
7typedef struct {
8char *result;
9int done;
10pthread_mutex_t mu;
11pthread_cond_t cond;
12} Flight;
13
14Flight *flight_new() {
15Flight *f = malloc(sizeof(Flight));
16f->result = NULL;
17f->done = 0;
18pthread_mutex_init(&f->mu, NULL);
19pthread_cond_init(&f->cond, NULL);
20return f;
21}
22
23void flight_set_result(Flight *f, const char *result) {
24pthread_mutex_lock(&f->mu);
25f->result = malloc(strlen(result) + 1);
26strcpy(f->result, result);
27f->done = 1;
28pthread_cond_broadcast(&f->cond);
29pthread_mutex_unlock(&f->mu);
30}
31
32const char *flight_wait(Flight *f) {
33pthread_mutex_lock(&f->mu);
34while (!f->done) {
35pthread_cond_wait(&f->cond, &f->mu);
36}
37const char *res = f->result;
38pthread_mutex_unlock(&f->mu);
39return res;
40}
41
42void flight_free(Flight *f) {
43if (f->result) free(f->result);
44pthread_mutex_destroy(&f->mu);
45pthread_cond_destroy(&f->cond);
46free(f);
47}
48
49Flight *cache = NULL;
50
51void *expensive_work(void *arg) {
52printf("[Thread %ld] İş başladı\n", (long)pthread_self());
53sleep(2);
54printf("[Thread %ld] İş bitti\n", (long)pthread_self());
55return NULL;
56}
57
58const char *get_data() {
59if (!cache) {
60cache = flight_new();
61pthread_t tid;
62pthread_create(&tid, NULL, expensive_work, NULL);
63pthread_detach(tid);
64sleep(2);
65flight_set_result(cache, "Veri: 42");
66}
67return flight_wait(cache);
68}
69
70int main() {
71printf("İstek 1: %s\n", get_data());
72printf("İstek 2: %s\n", get_data());
73printf("İstek 3: %s\n", get_data());
74
75if (cache) flight_free(cache);
76return 0;
77}

Beklenen çıktı:

1[Thread...] İş başladı
2[Thread...] İş bitti
3İstek 1: Veri: 42
4İstek 2: Veri: 42
5İstek 3: Veri: 42

Kodda Flight yapısı bir "uçuş" (işlem) temsil eder. İlk çağrıda cache boş olduğu için yeni bir Flight oluşturulur ve işlem başlatılır. Diğer çağrılar flight_wait ile sonucu bekler. Mutex işlem sırasında veri tutarlılığını sağlar, condition variable ise bekleyenleri uyandırır.

Sık yapılan hata: cache'i kontrol ettikten sonra mutex kilitlemeden Flight oluşturmak. Bu race condition'a neden olur ve birden fazla işlem başlatılabilir. Güvenli kullanım için tüm kontrol ve oluşturma işlemini mutex içinde yapmalısın.

Reklam · TEST
AdSense Test ReklamıGönderi altı · Görüntülü
konu hakkında tepkinizi verebilirsiniz
  • adlı kullanıcıya yanıt verdi
    phpenguen

    işte bu yüzden C'de yazılan kod bir kere öğrenilince diğer dillerdeki high-level şeyler çok basit geliyor, mekanizması anlaşıldığında

  • adlı kullanıcıya yanıt verdi
    biriktirdiklerim

    aynen de mesele şu işte C öğrenince başka dillerde ne yaptığını anlıyorsun Goda goroutineler çalışıyor ama arkada ne oluyor bilmiyorsun Cde ise her şeyi kendin yönetiyorsun

  • adlı kullanıcıya yanıt verdi
    phpenguen

    haklı, sistem programlama ya da embedded tarafında C şart zaten, Go'da da bu seviyeye inemezsin

  • adlı kullanıcıya yanıt verdi
    biriktirdiklerim

    evet ama Cde yazılması gereken yerler var Go güzel ama her yerde kullanılamıyor Üstelik patternin mantığını C ile öğrenmek daha faydalı bence Goda işte sihirli oluyor

  • phpenguen

    Mutex ve condition variable kombinasyonu güzel, ama C'de bu kadar düşük seviyede çalışmak yorucu. Aynı işlemi Go'da goroutine ve channel ile yazsaydın çok daha temiz olurdu.