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:
1#include <stdio.h>2#include <stdlib.h>3#include <string.h>4#include <pthread.h>5#include <unistd.h>67typedef struct {8char *result;9int done;10pthread_mutex_t mu;11pthread_cond_t cond;12} Flight;1314Flight *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}2223void 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}3132const 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}4142void flight_free(Flight *f) {43if (f->result) free(f->result);44pthread_mutex_destroy(&f->mu);45pthread_cond_destroy(&f->cond);46free(f);47}4849Flight *cache = NULL;5051void *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}5758const 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}6970int main() {71printf("İstek 1: %s\n", get_data());72printf("İstek 2: %s\n", get_data());73printf("İstek 3: %s\n", get_data());7475if (cache) flight_free(cache);76return 0;77}Beklenen çıktı:
1[Thread...] İş başladı2[Thread...] İş bitti3İstek 1: Veri: 424İstek 2: Veri: 425İstek 3: Veri: 42Kodda 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.
5 yorum
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.
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
haklı, sistem programlama ya da embedded tarafında C şart zaten, Go'da da bu seviyeye inemezsin
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
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