eGospodarka.pl
eGospodarka.pl poleca

eGospodarka.plGrupypl.comp.programming › pytanie z mutexów
Ilość wypowiedzi w tym wątku: 94

  • 91. Data: 2013-07-03 02:29:38
    Temat: Re: pytanie z mutexów
    Od: Michoo <m...@v...pl>

    On 02.07.2013 23:06, Edek wrote:
    > Dnia pamiętnego Tue, 02 Jul 2013 21:18:51 +0200, Michoo wyjmując peta
    > oznajmił:
    >
    >> On 02.07.2013 19:56, Edek wrote:
    >>> Dnia pamiętnego Tue, 02 Jul 2013 18:16:18 +0200, Michoo wyjmując peta
    >>> oznajmił:
    >>
    >>>> - w przypadku odczytu INIT_DONE pomijamy cały blok - inicjalizacja
    >>>> została już wykonana
    >>>
    >>> Tak, ale nie miała miejsce synchronizacja.
    >>
    >> Jaka synchronizacja?
    >
    > No żeby udowodnić widzialność danych musisz udowodnić odpowiedni
    > 'ordering' pomiędzy wątkami.

    No i memory barrier jest definiowane jako:
    - wszystkie zapisy wykonane do tej pory zostają commitowane do pamięci
    - wszystkie odczyty od tej pory czytają ostatnio zapisany stan

    >
    >>>> Pisane na szybko więc co przeoczyłem?
    >>>
    >>> Model pamięci C++11?
    >>
    >> pthreads z nie_pojebaną_architekturą (tm)
    >
    > ARM i Itanium się kwalifikują jako poyebane? IMHO tak.

    Imo nie. Nadal jak robisz barrierę to wszystkie zapisy są widoczne.

    >
    >>> Model Pamięci ma taką cechę że zapis w wątku A jest widoczny w wątku B
    >>> gdy nastąpi uszeregowanie poprzez release w A i acquire w B. Lock() to
    >>> aquire, unlock() to release, albo można w ogóle każdą operację na
    >>> mutexie traktować 'stricter' jako sequence point - czyli jeżeli oba
    >>> wątki przejdą op na mutex to zapis w A może być odczytany w B.
    >>
    >> Gwarancja jest oidp silniejsza - jeżeli wątek kończy release to zmiany
    >> są "commited" w pamięci.
    >
    > Nie bardzo. Gwarancje dotyczą widoczności pomiędzy wątkami, nie
    > ma Jednego Prawdziwego Stanu Pamięci. Nie znam finalnego standartu, ale
    > takie były proposale.

    Nawet w sytuacji gdy zapis jest "rozgłaszany" do odpowiedniego procesu
    dopiero w momencie wywołania którejś z funkcji synchronizacyjnych masz
    sytuację w której:
    - nie możesz czytać obiektów przed func() bo nie zostały zainicjalizowane
    - pierwszy odczyt powinien dostarczyć to co zostało commitnięte w
    ostatniej synchronizacji
    więc albo nie masz zsynchronizowanej wartości *once i wchodzisz do
    muteksu, albo masz zsynchronizowaną wartość *once ale wtedy masz
    zsynchronizowane też efekty func(). Wiem o write-reordering, ale barrier
    to barrier.

    Potraktuj to po prostu jako założenie(co napisałem) - tak jak autor
    oryginalnego algorytmu założył, że zapisy są "ordered" tak ja zakładam,
    że barrier znaczy barrier a nie niewiadomo-co.

    >
    >>> Jeżeli
    >>> nie ma przejścia przez mutex obu wątków mamy race, czyli wątek B
    >>> czyta potencjalnie śmieci.
    >>
    >> Czyta albo stary stan, albo nowy stan[*] a nie śmieci - dlatego m.i. się
    >> używa sig_atomic_t - pierwotne rozwiązanie też to zakłada.
    >
    > A dlaczego wszystkie pola ustawiane przez func miałyby używać
    > sig_atomic_t? Nie doczytałeś, nie chodzi o zmienne
    > użyte w tym algorytmie, ale dowolne inicjalizowane przez func
    > a używane po przejściu tego algorytmu. Czyli w tym algorytmie ich
    > nie ma, są powodem istnienia once().

    Jest barriera przed zapisem *once, które jest atomowe. barriera jest
    ordered z operacją atomową, więc kolejność jest jasno zdefiniowana.

    >
    > Naprawdę wszystkie Twoje obiekty w polach statycznych w C++ używają
    > wszędzie sig_atomic_t? Ja tam wolę float czy int i parę innych.
    >
    >> [*] W praktyce nawet na NUMA jest utrzymywana spójność cache więc od
    >> momentu zapisu z cache do pamięci wszystkie odczyty będą miały nowy stan.
    >
    > Na Intelach tak, sam mówiłem, że na Intelach i tak będzie ok.

    Nie pisałem o intelach - one zazwyczaj jednak są SMP.


    > C++
    > ma działać na innych architekturach też. W tym takich, które powstaną
    > za 10 lat, najlepiej bez losowych fackapów. Jak tak słuchałem
    > Ludzi Który Wiedzą Co Mówią, ARM, Itanium, Alphy są inne niż x86 ;)
    > Bardzo Inne (tm).

    Zaprezentowany algorytm jeżeli ma serializację odczytu to zgłosi wyjątek
    w momencie zapisu - coś jak javowe ConcurrentModificationException -
    pamięć została odczytana w sposób konfliktujący z zapisem.

    >
    >>> Zapis dotyczy zmiennych użytych tutaj i wszystkich
    >>> zapisów w func(), które po przejściu algorytmu muszą być widoczne.
    >>
    >> Masz barrierę na lock() w linii 07 zaraz po wywołaniu func() a dopiero
    >> potem modyfikację stanu zmiennej w 08.
    >>
    >>>
    >>> Uff, definicje z głowy ;). Wątek A i wątek B i numery linii,
    >>> możliwa sekwencja:
    >>>
    >>> A linie 1 do 5 (w 2 i 5 jest mutex)
    >>> A 6: konstruktor (po to jest ten algorytm) może zapisać pola
    >>> A 7: mutex (acquire)
    >>> A 8: once = INIT_DONE
    >>>
    >>> B 1: (once == INIT_DONE) == true (co nie musi być prawdą, ale może)
    >>
    >> lock() synchronizuje pamięć
    >
    > Nie no proszę cię. Jak się synchronizuje dostęp do danych w dwóch
    > wątkach to trzeba w obu użyć synchronizacji.

    Z tego wynika, że mutex_lock robi synchronizację pamięci:
    http://pubs.opengroup.org/onlinepubs/9699919799/base
    defs/V1_chap04.html#tag_04_11

    Drugi wątek ma prawo zrobić odczyt inicjalizowanych obiektów dopiero PO
    zakończeniu once().

    >
    > Czy ty mnie nie prowokujesz?

    Ależ oczywiście - wiem czego "unika" ta implementacja i uważam, że żadna
    istniejąca teraz architektura ani żadna która zaistnieje w przyszłości
    jeżeli daje gwarancję "serializowalności" to tym bardziej daje gwarancję
    uszeregowania barrier. Po prostu uważam, że w sprytny sposób unikany
    jest problem, który nie istnieje.

    >
    >>> B 17: wątek używa wartości z A 6, które nie muszą być poprawne,
    >>> bo wątek B nie przeszedł przez żaden mutex. Może odczytać
    >>> śmieci, czyli mamy race
    >>
    >> No właśnie nie może, bo to oznacza, że masz architekturę na której
    >> zmiany wykonane po barrierze są widoczne przed zmianami wykonanymi przed
    >> barrierą.
    >
    > Inny punkt widzenia jest taki, że drugi wątek może czytać w innej
    > kolejności, skoro nie ma pomiędzy tymi punktami synchronizacji.

    Nie wolno mu czytać danych, które są dopiero inicjalizowane w once() do
    jego zakończenia - to by było UB. Skoro czyta nową wartość *once, które
    było modyfikowane PO barrierze to w dowolnej sensownej implementacji (to
    było założenie poprawności mojego algorytmu) dane zostaną commitnięte.

    >
    > I nie, Intel tego nie zabrania, bo kompilator może inaczej
    > szeregować operacje, jeżeli tak mu wygodnie podczas optymalizacji.

    Kompilator też zna coś takiego jak barriera - przenośna biblioteka
    zgodna z pthreads powinna to używać w funkcjach synchronizujących.

    >
    >>> Powyższa sekwencja może jest mało prawdopodobna, ale mamy
    >>> data race.
    >>
    >> Jeżeli chcesz się trzymać litery standardu C++11 to sam odczyt w linii
    >> 01 oryginalnego rozwiązania to jest data race, więc całe dalsze
    >> wykonanie to UB.
    >
    > Co najwyżej może podlegać word-tearing, ale używają typu,
    > który ustawiany jest cały (czyli atomicznie, ale z najsłabszym
    > ordering czyli żadnym).

    Nie. Użyty jest dostęp przez wskaźnik a nie jeden z konstruktów c++11.
    Standard wyraźnie mówi, że:
    - równoczesny dostęp i zapis są "confilicting"
    - "confliccting" bez synchronizacji z obu stron to UB

    >
    > Sam fakt że może widzieć starą wartość algorytm uwzględnia i to nie
    > jest UB.

    Ale UB jest sam odczyt.

    > Z wątkami tak jest, albo jeden może coś zrobić wcześniej a
    > drugi później albo odwrotnie i czy atomik już jest ustawiony
    > czy jeszcze nie nie jest żadnym UB.

    No i w moim też tak jest - najpierw jest niejawna barriera a dopiero
    potem atomic - jeżeli atomic jest widoczny to stan z przed barriery też.

    >
    > Swoją szosą UB stosuje się do reguły "as-if program działa w jednym
    > wątku".

    ???

    >
    >>> Dlatego ten algorytm jest genialny, kolejne
    >>> przejścia są bez synchronizacji.
    >>
    >> Powiedziałbym, że jest "normalny" - działa w obrębie pewnych założeń.
    >> Tyle, że rozwiązuje problem, którego imo nie ma przez nałożenie
    >> ograniczenia na liczbę once a przy tym nie eliminując problemu z UB.
    >
    > Ok, algorytm nie jest może wiekopomny, ale jak widać dowodzenie
    > jego poprawności nie jest takie trywialne.

    Jak czytałeś algorytmy stosowane w bazach danych to jest.
    Poza tym algorytm opiera się na założeniu, że odczyt konfliktujący z
    zapisem jest ok. A ty twierdzisz, że odczyt po barrierze nie gwarantuje
    spójności danych przed barrierą jeżeli barriera nie była wołana w obu
    wątkach. Czemu jedno założenie ma być lepsze od innego?

    --
    Pozdrawiam
    Michoo


  • 92. Data: 2013-07-03 04:08:36
    Temat: Re: pytanie z mutexów
    Od: Edek <e...@g...com>

    Dnia pamiętnego Wed, 03 Jul 2013 02:29:38 +0200, Michoo wyjmując peta
    oznajmił:

    > On 02.07.2013 23:06, Edek wrote:
    >> Dnia pamiętnego Tue, 02 Jul 2013 21:18:51 +0200, Michoo wyjmując peta
    >> oznajmił:
    >>
    >>> On 02.07.2013 19:56, Edek wrote:
    >>>> Dnia pamiętnego Tue, 02 Jul 2013 18:16:18 +0200, Michoo wyjmując peta
    >>>> oznajmił:
    >>>
    >>>>> - w przypadku odczytu INIT_DONE pomijamy cały blok - inicjalizacja
    >>>>> została już wykonana
    >>>>
    >>>> Tak, ale nie miała miejsce synchronizacja.
    >>>
    >>> Jaka synchronizacja?
    >>
    >> No żeby udowodnić widzialność danych musisz udowodnić odpowiedni
    >> 'ordering' pomiędzy wątkami.
    >
    > No i memory barrier jest definiowane jako:
    > - wszystkie zapisy wykonane do tej pory zostają commitowane do pamięci
    > - wszystkie odczyty od tej pory czytają ostatnio zapisany stan

    Ale wątku B w tej sekwencji NIE MA BARRIERY. Jest:

    if (false) {
    }
    continue happily.

    Każdy kompilator może zamienić kod taki:
    ----------------
    if (sth){
    jakaś_funkcja()
    }
    zrobB
    -----------------

    na taki:

    ------------
    wczytaj_dane_doB
    if (sth){
    jakas_funkcja();
    wczytaj_dane_doB
    }
    uzyj_danych_dokonczB
    -------------

    Efekt jednego i drugiego kodu według as-if jest identyczny.

    Niezależnie od architektury sprzętu jest to legalna i nawet dość
    sensowna optymalizacja, przy spekulatywnym wykonaniu chowa się
    latency danych B a nawet lepiej. W oryginalnym algorytmie n2660
    będzie ok, w zmodyfikowanym dane_doB są czytane za wcześnie.

    Ja w ogóle nie bardzo rozumiem w Twoim wywodzie jednej rzeczy:
    skoro wątek B nie ma żadnej barriery i procesor ma pipeline,
    to w którym stadium pipeline procesora z wątkiem B musi
    nastąpić barriera wątku A i dlaczego?

    >>>>> Pisane na szybko więc co przeoczyłem?
    >>>>
    >>>> Model pamięci C++11?
    >>>
    >>> pthreads z nie_pojebaną_architekturą (tm)
    >>
    >> ARM i Itanium się kwalifikują jako poyebane? IMHO tak.
    >
    > Imo nie. Nadal jak robisz barrierę to wszystkie zapisy są widoczne.

    Może powiedz co rozumiesz przez słowo barriera. To że jeden
    wątek coś robi nie ma żadnego magicznego związku z innymi wątkami. Musi
    być happens-before, coś w wątku A przed czymś w wątku B wraz z memory
    barrier. A tu w wątku B jest tylko odczyt *once, który nie ma semantyki
    memory barrier.

    >>>> Model Pamięci ma taką cechę że zapis w wątku A jest widoczny w wątku B
    >>>> gdy nastąpi uszeregowanie poprzez release w A i acquire w B. Lock() to
    >>>> aquire, unlock() to release, albo można w ogóle każdą operację na
    >>>> mutexie traktować 'stricter' jako sequence point - czyli jeżeli oba
    >>>> wątki przejdą op na mutex to zapis w A może być odczytany w B.
    >>>
    >>> Gwarancja jest oidp silniejsza - jeżeli wątek kończy release to zmiany
    >>> są "commited" w pamięci.
    >>
    >> Nie bardzo. Gwarancje dotyczą widoczności pomiędzy wątkami, nie
    >> ma Jednego Prawdziwego Stanu Pamięci. Nie znam finalnego standartu, ale
    >> takie były proposale.
    >
    > Nawet w sytuacji gdy zapis jest "rozgłaszany" do odpowiedniego procesu
    > dopiero w momencie wywołania którejś z funkcji synchronizacyjnych masz
    > sytuację w której:
    > - nie możesz czytać obiektów przed func() bo nie zostały zainicjalizowane
    > - pierwszy odczyt powinien dostarczyć to co zostało commitnięte w
    > ostatniej synchronizacji
    > więc albo nie masz zsynchronizowanej wartości *once i wchodzisz do
    > muteksu, albo masz zsynchronizowaną wartość *once ale wtedy masz
    > zsynchronizowane też efekty func(). Wiem o write-reordering, ale barrier
    > to barrier.

    A gdzie jest ta bariera?

    > Potraktuj to po prostu jako założenie(co napisałem) - tak jak autor
    > oryginalnego algorytmu założył, że zapisy są "ordered" tak ja zakładam,
    > że barrier znaczy barrier a nie niewiadomo-co.

    Gdzie autor robił takie założenie?

    Główny powód dla którego nie miesza się sprzętu do logiki poprawności
    w abstrakcyjnym modelu jest to, że wiele elementów zachowuje się zgodnie
    z tym modelem i w ramach tego modelu może robić co jest akurat potrzebne
    - na przykład kompilator optymalizuje. Wolno mu, taki jest model,
    a ty zakładasz, że kompilator nic nie robi i wszystko jest zgodne na
    poziomie języka z właściwościami sprzętu. Nie jest.

    >>>> Jeżeli
    >>>> nie ma przejścia przez mutex obu wątków mamy race, czyli wątek B
    >>>> czyta potencjalnie śmieci.
    >>>
    >>> Czyta albo stary stan, albo nowy stan[*] a nie śmieci - dlatego m.i. się
    >>> używa sig_atomic_t - pierwotne rozwiązanie też to zakłada.
    >>
    >> A dlaczego wszystkie pola ustawiane przez func miałyby używać
    >> sig_atomic_t? Nie doczytałeś, nie chodzi o zmienne
    >> użyte w tym algorytmie, ale dowolne inicjalizowane przez func
    >> a używane po przejściu tego algorytmu. Czyli w tym algorytmie ich
    >> nie ma, są powodem istnienia once().
    >
    > Jest barriera przed zapisem *once, które jest atomowe. barriera jest
    > ordered z operacją atomową, więc kolejność jest jasno zdefiniowana.

    Żeby kolejność była zdefiniowa, musi zaistnieć happens-before, czyli
    mamy 'barierę' w jednym wątku przed 'barierą' w drugim wątku. A tu
    jest tylko w jednym, więc właśnie nie jest zdefiniowana wcale.

    Zresztą na procesorze ogólnie jest podobnie, tylko x86 ma dość ścisły
    model pamięci. Tak naprawdę pod tym względem x86 jest bardzo
    tolerancyjnym sprzętem, dlatego działały wszystkie akcje typu
    while(a==0) {}; jako cond.wait().

    > Zaprezentowany algorytm jeżeli ma serializację odczytu to zgłosi wyjątek
    > w momencie zapisu - coś jak javowe ConcurrentModificationException -
    > pamięć została odczytana w sposób konfliktujący z zapisem.

    Jaką serializację odczytu?

    Tak btw, ConcurrentModificationException oznacza zmiany strukturalne
    w kontenerze i użycie 'starego' iteratora, niekoniecznie ma związek z wątkami.

    > Z tego wynika, że mutex_lock robi synchronizację pamięci:
    > http://pubs.opengroup.org/onlinepubs/9699919799/base
    defs/V1_chap04.html#tag_04_11

    Jak rozumiesz "with respect to other threads"? Najwyraźniej inaczej niż ja.

    > Drugi wątek ma prawo zrobić odczyt inicjalizowanych obiektów dopiero PO
    > zakończeniu once().

    Tak być powinno.

    >> Czy ty mnie nie prowokujesz?
    >
    > Ależ oczywiście - wiem czego "unika" ta implementacja i uważam, że żadna
    > istniejąca teraz architektura ani żadna która zaistnieje w przyszłości
    > jeżeli daje gwarancję "serializowalności" to tym bardziej daje gwarancję
    > uszeregowania barrier. Po prostu uważam, że w sprytny sposób unikany
    > jest problem, który nie istnieje.

    Ok...

    Chcesz uważać że llvm i gcc dla c++11 mają UB i to jest zapisane
    w standardzie że mają mieć zgodnie z n2660 - wolna wola, uchylę się od
    dalszych odpowiedzi.

    >>>> B 17: wątek używa wartości z A 6, które nie muszą być poprawne,
    >>>> bo wątek B nie przeszedł przez żaden mutex. Może odczytać
    >>>> śmieci, czyli mamy race
    >>>
    >>> No właśnie nie może, bo to oznacza, że masz architekturę na której
    >>> zmiany wykonane po barrierze są widoczne przed zmianami wykonanymi przed
    >>> barrierą.
    >>
    >> Inny punkt widzenia jest taki, że drugi wątek może czytać w innej
    >> kolejności, skoro nie ma pomiędzy tymi punktami synchronizacji.
    >
    > Nie wolno mu czytać danych, które są dopiero inicjalizowane w once() do
    > jego zakończenia - to by było UB. Skoro czyta nową wartość *once, które
    > było modyfikowane PO barrierze to w dowolnej sensownej implementacji (to
    > było założenie poprawności mojego algorytmu) dane zostaną commitnięte.

    Oryginalna implementacja jest zgodna ze standardem. Spróbuj może znaleźć
    odpowiednie zapisy w standarcie c++11 na poparcie Twojej tezy.

    >> I nie, Intel tego nie zabrania, bo kompilator może inaczej
    >> szeregować operacje, jeżeli tak mu wygodnie podczas optymalizacji.
    >
    > Kompilator też zna coś takiego jak barriera - przenośna biblioteka
    > zgodna z pthreads powinna to używać w funkcjach synchronizujących.

    No ale jak w wątku B nie ma barriery, to kompilator ma ją wymyśleć?

    Poza tym pthreads miało rację bytu przed c++11 i kompilator jednak
    nie zwracał większej uwagi na pthreads. Po prostu zakładał, że
    każda nieznana funkcja może zrobić cokolwiek - to już załatwia
    potencjalne problemy z pthreads. Istniał instrinsic chyba __mb(),
    ale on nic nie robił poza wstawianiem odpowiednich instrukcji.
    Kompilator przed C++11 sprawę wątków centralnie olewał, bo mógł.

    > Nie. Użyty jest dostęp przez wskaźnik a nie jeden z konstruktów c++11.
    > Standard wyraźnie mówi, że:
    > - równoczesny dostęp i zapis są "confilicting"
    > - "confliccting" bez synchronizacji z obu stron to UB

    Któryś z nas jest mocno niewyspany. Jaki konstruktor?

    Atomics z definicji są dostępne bez innej synchronizacji. Nawet na
    Intelach niektóre orderings zaimplementowane są przez
    mov value, [addr]
    ... bo skutek jest zgodny z modelem pamięci.

    >> Sam fakt że może widzieć starą wartość algorytm uwzględnia i to nie
    >> jest UB.
    >
    > Ale UB jest sam odczyt.

    Bzdura. Dlaczego odczyt atomika miałby być UB?

    >> Z wątkami tak jest, albo jeden może coś zrobić wcześniej a
    >> drugi później albo odwrotnie i czy atomik już jest ustawiony
    >> czy jeszcze nie nie jest żadnym UB.
    >
    > No i w moim też tak jest - najpierw jest niejawna barriera a dopiero
    > potem atomic - jeżeli atomic jest widoczny to stan z przed barriery też.

    Ano nie, standard przewiduje różne 'ordering' tych samych operacji. Dla
    niektórych jest tak jak mówisz, dla innych nie. Jest, własnymi słowami,
    1. sequence point (czyli Twoja bariera), 2. Acquire semantics
    3. Release semantics (połowiczne barriery) i 4. Zapisz po prostu
    atomicznie wartość bez żadnego szeregowania. *once ma tę ostatnią.

    >> Swoją szosą UB stosuje się do reguły "as-if program działa w jednym
    >> wątku".
    >
    > ???

    Nigdy nie słyszałem, żeby określenie UB było stosowane do błędów
    związanych z wielowątkowością. Przeciwnie, przed C++11 podstawowym
    założeniem kompilatora było to, że kompilowany kod jest jednowątkowy
    i tylko miejscami zawiera wywołanie nieznanej funkcji, która może
    zrobić cokolwiek. I w ramach jednego wątku działa reguła as-if,
    na której opierają się wszystkie optymalizacje, czyli wynik
    [jednowątkowego] kodu musi być taki sam jak bez optymalizacji.
    Kolejna reguła mówi, że kompilator może założyć że nie ma UB
    (a jeżeli jest to programista ma problem tak czy inaczej).

    Zwróć uwagę, że optymalizacja może zmienić kolejność dowolnych
    odczytów i zapisów do pamięci jeżeli wynik jest taki sam,
    o ile nie ma pomiędzy nimi wywołania nieznanej funkcji. I cała
    Twoja bariera wyparowuje, gdy jej nie ma w wątku B, podobnie
    uszeregowanie odczytów - kompilator nic o tym nie wie i zmieni
    kolejność jak mu wygodnie.

    > Jak czytałeś algorytmy stosowane w bazach danych to jest.
    > Poza tym algorytm opiera się na założeniu, że odczyt konfliktujący z
    > zapisem jest ok. A ty twierdzisz, że odczyt po barrierze nie gwarantuje
    > spójności danych przed barrierą jeżeli barriera nie była wołana w obu
    > wątkach. Czemu jedno założenie ma być lepsze od innego?

    Prawdziwe założenie bywa lepsze od fałszywego. Możemy się
    oprzeć co najwyżej na standarcie C++11.

    --
    Edek


  • 93. Data: 2013-07-09 14:42:17
    Temat: Re: pytanie z mutexów
    Od: firr <p...@g...com>

    > Owszem, ale to dziala w obie strony.

    to sa dwa różne działy programowania mozna by
    powiedziec (takie w ktorych zaklada sie wiedze
    o tym jak dzialaja nizsze warstwy i takie
    gdzie zaklada sie ? brak takiej wiedzy)

    dla mnie podstawowe pytanie dotyczace (2)
    jest takie czy i tak tego co w (1) jest
    po prostu wiedza na temat nizszych warstw
    tutaj nie trzeba zastapic czyms innym (naprzyklad
    zbiorem dennych reguł albo testami, bvez tej
    wiedzy programowanie w takim (2) robi sie czasem
    jakby babraniem w niewiadmych (traci sie poczucie
    jakosci i wogole jest masa chały - duzo zalezy
    np od tego czy ta liste reguł jest długa czy
    nie ;-)


  • 94. Data: 2013-07-10 15:41:05
    Temat: Re: pytanie z mutexów
    Od: firr <p...@g...com>

    > >>(choćby lubiane prze mnie producent-konsument
    >

    :/\ //niesmak (distaste)

strony : 1 ... 9 . [ 10 ]


Szukaj w grupach

Szukaj w grupach

Eksperci egospodarka.pl

1 1 1

Wpisz nazwę miasta, dla którego chcesz znaleźć jednostkę ZUS.

Wzory dokumentów

Bezpłatne wzory dokumentów i formularzy.
Wyszukaj i pobierz za darmo: