Ipari zen

Szak-irodalom. A fejlesztő jajszava haikuban. A technika ördöge avagy a lelketlen vasban a mélyen emberi.

A szoftverfejlesztés művészete és a zen

2010.08.05. 18:20 Ipari zen

A kínai típusú túlkínálat

Címkék: producer mutex multithreading szemafor consumer

Elöl állsz végre

egy sorban? Lendületből

előz a másik.

Érdekes túlkínálati válság alakult ki Kínában a 90-es években. A termelés hihetetlen iramban nőtt, a fizetőképes kereslet is jelentősen növekedett, egyetlen dolog hiányzott ahhoz hogy a belső piac hatalmas ütemben bővülni kezdjen. A szabadidő. Ugyanis a feszített munkatempó, a hatnapos munkahét és hivatalos ünnepek hiánya miatt a dolgozók nem tudták elkölteni amúgy szépen gyarapodó megtakarításaikat. A párt persze lépett és 1995-ben bevezették az ötnapos munkahetet, majd 2000-ben az arany hetek néven ismert 3 egyhetes nemzeti ünnepet.

No valami hasonló állapotot sikerült előállítani nemrég egy rosszul sikerült consumer/producer implementációval nem olyan régen.

Az implementáció 3 buffert használt ami körkörösen körbe voltak láncolva, az egyik szál töltögette őket sorjában, a másik pedig ürítette (volna) őket egymás után. Minden buffernek saját szemaforja van, amit producer és consumer szál is kizárólagosan lefoglal az töltés/ürítés idejére. Melékelem a kódrészleteket is a könnyebbség kedvéért.

#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>

pthread_t read_from_buffer_thread;

typedef struct buffer
{
        sem_t semaphore;
        char* data;
        size_t offset;
        struct buffer* nextBuffer;
} buffer;

#define NUMBER_OF_BUFFERS 3
buffer buffers[NUMBER_OF_BUFFERS];
buffer* currentBuffer = NULL;

void write_to_buffer(char* data, size_t size)
{
    if((currentBuffer->offset + size) > BUFFER_MAX_SIZE || file != currentBuffer->file)
    {
        // switch buffer
        sem_post(&(currentBuffer->semaphore));
        sem_wait(&(currentBuffer->nextBuffer->semaphore));

        currentBuffer = currentBuffer->nextBuffer;
    }
    fill_buffer_with_data(currentbuffer, data, size);
}

void* read_from_buffer_thread_function(void* parameters)
{
        buffer* buffer = &(buffers[0]);
        while(active)
        {
            sem_wait(&(buffer->semaphore));
            extract_data_from_buffer(buffer->data, buffer->offset);
            buffer->offset = 0;
            sem_post(&(buffer->semaphore));
            buffer = buffer->nextBuffer;
        }
}

void init_buffers(size_t bufferMaxSize)
{
    int i = 0;
    for(i = 0; i < NUMBER_OF_BUFFERS; i++)
    {
        currentBuffer = &(buffers[i]);
        sem_init(&(currentBuffer->semaphore), 0, 1);
        currentBuffer->offset = 0;
        currentBuffer->nextBuffer = ( i < NUMBER_OF_BUFFERS-1 ? &(buffers[i+1]) : &(buffers[0]));
    }
    currentBuffer = &(buffers[0]);
    sem_wait(&(currentBuffer->semaphore));
}

#define N 1048576

int main()
{
    init_buffers();
    active = 1;
    pthread_create(&read_from_buffer_thread, NULL, read_from_buffer_thread_function, NULL);
    while(active)
    {
        write_to_buffer(data, N);
    }
}
Na mármost, a rendszer kiválóan működött egészen addig míg a consumer és producer száll párhuzamosan tudott futni és a consumer szál a szemafor miatt várakozásra nem kényszerült. Akkor pihenőpályára került a lezárt szemafor miatt, a producer szál pedig vidáman töltögette a buffert. Az elvárt működés szerint a producer szál a buffer megteltével átugrik a következő bufferre, a consumer szál pedig felébred csipkerózsika álmából és szépen elkezdené fogyasztani az éppen befejezett buffert. Igenám, csakhogy a meglehetősen gyors töltődés miatt, mire a consumer szál hármasban öblögetve kivánszorgott a boxutcából és lefoglalta volna a frissen elengedett szemafort, a nagy lendülettel köröző producer szál egy teljes kör megtétele után a rázókövön előzve újra lefoglalta a nemrég általa elengedett szemafort. Következésképpen teleírta a még ki nem ürített buffert, aminek persze elszállás lett a vége. Levonható a tanulság ami a POSIX szabvány ismeretében kézenfekvő, de némiképp ellenkezik a mindennapi tapasztalatainkkal, tehát a kezdő simán megszívja.

Amikor a szemafort/lockot/mutexet elengedi egy szál, mindössze annyi történik, hogy a többi szál, aki erre várt, futtatható állapotba kerül. Hogy közülük melyik lesz az aki a valóban futni fog, azt a kernel scheduler mondja meg egy (a user által nem teljesen megjósolható eredményű) algoritmus alapján. Tehát a fejlesztő aki arra számít, hogy FIFO módon, az a szál foglalja el a szemafort, amelyik a legrégebben vár rá, úgy pofára esik mint  a székely bácsi, aki felajzva beront a NŐK feliratú ajtón, aztán odabent nő egy szál se, csak a WC.

A korrekt megoldás a jelelegi struktúrában, ha a  szemafort mutexekre cseréljük és egy feltételes várakozást állítunk be az offset null illetve nem null értkére.  Ez híven tükrözi a valóságot, miszerint az producer szál nem egyszserűen bufferre, hanem ÜRES bufferre, a consumer szál pedig TELE bufferre várakozik. Érdekes még, hogy kis furmánnyal  a jelenlegi állapot úgy is javítható, hogy az olvasó szál addig nem engedi el az üres buffert, míg nem kap cserébe egy telit.

A wiki oldalán különben elég jó általános leírás olvasható a klasszikus szinkronizálási problémákról. Ha pedig egy kicsit mélyebb elemzést szeretnél klasszikus, kevésbé klasszikus, nem annyira klasszikus és távolról sem klasszikus szinkronizálási problémákról akkor olvasd inkábbAllen B. Downey: The Little Book of Semaphores című ingyenesen letölthető könyvét.

2 komment

A bejegyzés trackback címe:

https://ipari-zen.blog.hu/api/trackback/id/tr52201704

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Sopánka76 2010.08.05. 22:30:34

Érdekes lehetett.
Jó, de néha mégse jó.
:(
Kicsit néha csaponganak a szerző gondolatai, de azért nem zavarták szét a ménest, tudtam követni. ;)

Ipari zen · http://ipari-zen.blog.hu 2010.08.06. 17:43:17

@Sopánka76: Főleg olyankor csaponganak nagyon, mikor hátast dob a blogmotor és utána újra kell írni a post háromnegyedét. dehát a blogmotor is csak egy ember, ahogy az ecceri haikuíró mondaná.