Co oznacza parametr volatile?

Co oznacza parametr volatile?
0

Czym różnią się poniższe funkcje?

Kopiuj
void USART_Puts(volatile char *s)

void USART_Puts(char *s)

Co zmienia parametr volatile?

Chciałbym jeszcze zaimplementować coś w rodzaju komend AT. Najpierw będę odbierał pojedyncze znaki w przerwaniu. Zastanawiam się czy jest potrzeba dodawać je do bufora kołowego i dopiero potem je parsować? Czy może bufor kołowy jest zbędny?

Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:około 10 godzin
0
  1. Chyba bufor cykliczny, a nie kołowy.
  2. Volatile oznacza by nie trzymać kopii zmiennej by używać jej w kolejnych instrukcjach. Zmienna volatile jest ładowana z pamięci przy każdej instrukcji, która z niej korzysta. Zmienne bez volatile mogą mieć np kopie przechowywane w rejestrach. Operacje działają wtedy na tych kopiach, ale tak, by nie zmienić semantyki programu w przypadku wykonania jednowątkowego.

"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
pingwindyktator
  • Rejestracja:ponad 12 lat
  • Ostatnio:około 2 miesiące
  • Lokalizacja:Kraków
  • Postów:1055
0

@Wibowit

Zmienna volatile jest ładowana z pamięci przy każdej instrukcji, która z niej korzysta.

To nie prawda. Obiekty volatile jak najbardziej mogą być cache'owane (https://en.wikipedia.org/wiki/Cache_invalidation).
volatile oznacza, że każdy dostęp do obiektu jest traktowany jako dostęp z side effects, stąd wyłączona zostaje optymalizacja as-if. Wynika z tego, że instrukcje na obiektach z volatile nie podlegają optymalizacji, nie mogą mieć zmienionej przez optymalizator semantyki czy porządku. Należy pamiętać, że taki obiekt nie jest jednak obiektem thread safe.


do not code, write prose
Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:około 10 godzin
0

To nie prawda. Obiekty volatile jak najbardziej mogą być cache'owane (https://en.wikipedia.org/wiki/Cache_invalidation).

Mogą sobie siedzieć w pamięci podręcznej procesora, ale nadal na poziomie instrukcji maszynowych mamy do czynienia ze zwykłym ładowaniem z pamięci z oryginalnej lokalizacji.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
pingwindyktator
  • Rejestracja:ponad 12 lat
  • Ostatnio:około 2 miesiące
  • Lokalizacja:Kraków
  • Postów:1055
0

Nie. Siedzą sobie w "pamięci podręcznej procesora" (L2 cache) i stamtąd są ładowane. Inaczej jaki sens miałoby ich cache'owanie?


do not code, write prose
Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:około 10 godzin
0

Napisałem:

na poziomie instrukcji maszynowych mamy do czynienia ze zwykłym ładowaniem z pamięci z oryginalnej lokalizacji.

Logiczne, że chodziło mi o o to, że w przypadku volatile za każdym razem mamy ładowanie z pamięci za pomocą:

Kopiuj
mov rejestr, [oryginalny adres]

a nie korzystamy z kopii, która np już siedzi sobie w jakimś rejestrze.

Coś nie pasi?


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:3 minuty
0

To nie prawda. Obiekty volatile jak najbardziej mogą być cache'owane (https://en.wikipedia.org/wiki/Cache_invalidation).

Szczegół implementacyjny danego komputera. Instrukcja jest generowana, i o to w volatile chodzi.

Należy pamiętać, że taki obiekt nie jest jednak obiektem thread safe.

Jest albo nie jest. Przeznaczeniem volatile nie jest synchronizacja wątków - gwarancje które daje samo w sobie są za słabe. Ostrożnie użyte może jednak do tego posłużyć.

edytowany 1x, ostatnio: Azarien
02
  • Rejestracja:prawie 14 lat
  • Ostatnio:około 8 lat
  • Postów:1176
0

W kontekście deklaracji funkcji znaczy także, że możesz przekazać volatile * jako parametr (w przeciwnym razie dostaniesz błąd kompilacji).

pingwindyktator
  • Rejestracja:ponad 12 lat
  • Ostatnio:około 2 miesiące
  • Lokalizacja:Kraków
  • Postów:1055
0

@Wibowit chyba faktycznie mówimy o tym samym. Istotne jednak, że obiekty volatile mogą być cache'owane.

@Azarien za Scott Meyers' Effective Modern C++:

volatile has nothing to do with concurrent programming. [...] It's thus wortwhile to discuss volatile in a chapter on concurrency if for no other reason than to dispel the confision surrounding it. [...] In contrast, the corresponding code using volatile guarantees virtually nothing in a multitheading context. [...] During execution of this code, if other threads are reading the value vi, they may see anything. Such code would have undefined behaviour, because these statements modify vi, so if other threads are reading vi at the same time, there are somultaneous readers and writers of memory that's neither std::atomic nor protected by a mutex, and that's the definition of a data race.


do not code, write prose
edytowany 1x, ostatnio: pingwindyktator
mlyszczek
  • Rejestracja:prawie 9 lat
  • Ostatnio:ponad 8 lat
  • Lokalizacja:Wrocław
  • Postów:167
0

Ja dodam jeszcze że volatile wykorzystuje się przede wszystkim w obiektach, które mogą się spontanicznie zmienić bez wiedzy kompilatora.

Rozważmy kod, który odbiera 3 bajtu z kontrolera uart

Kopiuj
{
uint8_t buf[3] = {0};
uint8_t *dr = (uint8_t *)0x40001005; // rejestr w którym zapisywany jest bajt odebrany po uart
buf[0] = *dr;
buf[1] = *dr;
buf[2] = *dr;
}

Po odczycie rejestru dr, kontroler zapisuje do rejestru dr następny bajt w kolejce (pomijam tutaj ustawienia flag). Kompilator tutaj może stwierdzić, że skoro w całym scopie, dr się nie zmienia, to on sobie tylko raz odczyta to co jest pod dr, a następnie będzie tylko brał wartość z rejestru. I dostajemy 3 takie same bajty - błędne.

Aby temu zaradzić do dr dodajemy słówko volatile. W takim przypadku, kompilator wie, że dr może się zmienić spontanicznie, bez udziału programu i każde odwołanie do komórki wskazującej przez dr, będzie zawsze pobierane bezpośrednio spod tego adresu.

KA
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 4 lata
  • Postów:192
0

@Azarien: Do programowania współbieżnego służą niskopoziomowe mechanizmy, takie jak atomowe operacje logiczne i arytmetyczne oraz grupy specjalizowanych instrukcji takich jak test-and-set, compare-and-swap oraz load-link/store-conditional. Do tego mogą dochodzić instrukcje monitora. Wszystko dostępne w C z poziomu rozszerzeń kompilatora z prefixami __builtin. W oparciu o te niskopoziomowe mechanizmy implementuje się to, co programiści na co dzień używają: semafory, blokady, blokady wirujące i inne im podobne, dostępne już w bibliotece C albo w innych bibliotekach systemowych.

Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:3 minuty
0

taa, wiedziałem że zaraz ktoś się czepi. napisałem że DA SIĘ wykorzystać volatile w wielowątkowości. ale napisałem też że nie do tego służy.

KA
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 4 lata
  • Postów:192
0

A możesz pokazać "ostrożne użycie volatile" do synchronizacji? Serio, powiedziałeś coś, co może być naprawdę interesujące ;)

pingwindyktator
@Azarien też chciałbym to zobaczyć
satirev
  • Rejestracja:prawie 14 lat
  • Ostatnio:około 4 lata
2

@kapojot niektóre kompilatory mają rozszerzenie, które wprowadza synchronizacje na poziomie volatile. Na przykład w MSVC masz opcję /volatile:ms https://msdn.microsoft.com/en-us/library/jj204392.aspx https://msdn.microsoft.com/en-us/library/12a04hfd.aspx
Generalnie jednak nie powinno się na tym polegać/używać tego, o ile nie piszesz kodu tylko na jedną platformę i nie kompilujesz go tylko jednym kompilatorem i masz pewność, że to się raczej nie zmieni w przyszłości.

KA
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 4 lata
  • Postów:192
0

@satirev: dzięki za wyjaśnienie. Nie używam VS, a gcc/clang nie dają takich gwarancji.

Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:3 minuty
0
kapojot napisał(a):

A możesz pokazać "ostrożne użycie volatile" do synchronizacji? Serio, powiedziałeś coś, co może być naprawdę interesujące ;)

Jeżeli wszystkie dane współdzielone między dwoma wątkami są oznaczone jako volatile to jest to bezpieczne.
Łatwo jednak o pomyłkę. Nie polecam.

edytowany 1x, ostatnio: Azarien
pingwindyktator
Jakieś źródła, dowody?
Azarien
@pingwindyktator: a dlaczego uważasz, że nie?
KA
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 4 lata
  • Postów:192
2

Prosty test:

Kopiuj
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define N 100000

volatile int sum = 0;

void *
thr_add(void *_unused)
{
	int i;

	for (i = 0; i < N; i++)
		sum += 1;

	return (NULL);
}

void *
thr_sub(void *_unused)
{
	int i;

	for (i = 0; i < N; i++)
		sum -= 1;

	return (NULL);
}

int
main()
{
	int ret;
	pthread_t t1, t2;

	ret = pthread_create(&t1, NULL, thr_add, NULL);
	assert(ret == 0);
	ret = pthread_create(&t2, NULL, thr_sub, NULL);
	assert(ret == 0);

	pthread_join(t1, NULL);
	pthread_join(t2, NULL);

	printf("%d\n", sum);

	return (0);
}

Daje:

Kopiuj
▶ ./test    
8484

▶ ./test
-3066

▶ ./test
29127

▶ ./test
-24111

▶ ./test
12583

▶ ./test
27027

Poprawna wersja (atomowe operacje) zawsze daje 0:

Kopiuj
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define N 100000

volatile int sum = 0;

void *
thr_add(void *_unused)
{
	int i;

	for (i = 0; i < N; i++)
		__sync_fetch_and_add(&sum, 1);

	return (NULL);
}

void *
thr_sub(void *_unused)
{
	int i;

	for (i = 0; i < N; i++)
		__sync_fetch_and_sub(&sum, 1);

	return (NULL);
}

int
main()
{
	int ret;
	pthread_t t1, t2;

	ret = pthread_create(&t1, NULL, thr_add, NULL);
	assert(ret == 0);
	ret = pthread_create(&t2, NULL, thr_sub, NULL);
	assert(ret == 0);

	pthread_join(t1, NULL);
	pthread_join(t2, NULL);

	printf("%d\n", sum);

	return (0);
}
Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:3 minuty
0
Kopiuj
        sum += 1;


        sum -= 1;

Źle i dobrze wiesz dlaczego źle.

edytowany 3x, ostatnio: Azarien
mlyszczek
  • Rejestracja:prawie 9 lat
  • Ostatnio:ponad 8 lat
  • Lokalizacja:Wrocław
  • Postów:167
0

Ten kod

Kopiuj
volatile int val = 0;

void fun(void) {
	++val;
}

kompiluje się do:

Kopiuj
val:
        .zero   4
fun():
        pushq   %rbp
        movq    %rsp, %rbp
        movl    val(%rip), %eax
        addl    $1, %eax
        movl    %eax, val(%rip)
        nop
        popq    %rbp
        ret

Wyciągając z tego kod operujący na zmiennej val

Kopiuj
movl    val(%rip), %eax
addl    $1, %eax
movl    %eax, val(%rip)

Operacja inkrementowania zmiennej ma 3 instrukcje, nawet dla volatile. Osobiście nie widzę przeciwwskazań aby przyszło przerwanie po załadowaniu wartości zmiennej do rejestru, a przed dodaniem 1 i przełączeniem na inny wątek, który też operuje na val. W związku z tym nie widzę możliwości aby móc okreslić volatile jako thread-safe.

Chyba, że jest jakaś sztuczka, która na to pozwala, ale kompilowałem z -std=c11, więc na pewno nie będzie to zachowanie zgodne ze standardem, więc nie można by na nim za bardzo polegać.

edytowany 1x, ostatnio: mlyszczek
Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:3 minuty
0

No bo tak się nie da.

"synchronizacja" to za dużo powiedziane. "współdzielenie danych" bardziej.

Kopiuj
volatile int progress;

void thread1()
{
    for (progress=1; progress<=100; progress++)
    {
        // długie obliczenia
    }
}

void thread2()
{
    do
    {
        showProgress(progress);
        delay(1000);
    }
    while (progress!=100);
}

Bez volatile teoretycznie zmienna progress może skoczyć z 0 na 100 dopiero pod koniec obliczeń.

Kod powyżej oczywiście ma race condition, bo nie wiemy jakie wartości progress zostaną wyświetlone. Ale to normalne we wszelkiego rodzaju paskach postępu.

KA
Race condition może i jest, ale w tym konkretnym przypadku to nie jest błąd.
mlyszczek
  • Rejestracja:prawie 9 lat
  • Ostatnio:ponad 8 lat
  • Lokalizacja:Wrocław
  • Postów:167
0

Zgadza się ale należy dodać, że takie współdzielenie ma sens tylko w przypadku kiedy jeden wątek ma prawa rw do zmiennej a wszystkie inne wątki mają prawa r, a w dodatku reszta wątków nie polega za bardzo na aktualnej wersji pola. Progress bar jest tutaj całkiem dobrym przykładem. Jak użyjemy słowa "współdzielenie", zamiast "synchronizacji" danych to w ograniczonych przypadkach można przyznać rację.

KA
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 4 lata
  • Postów:192
0
Azarien napisał(a)

Źle i dobrze wiesz dlaczego źle.

Mam nadzieję, że nie podejrzewasz mnie, że konstruując kontrprzykład dla tezy o zastosowaniu volatile do synchronizacji napisałem przypadkowo kod, w którym sam nie widziałbym błędów. Pewnie też z tego samego powodu pokazałem od razu poprawny kod z właściwym komentarzem, żeby ktoś nieświadomie nie użył tego śmiecia w praktyce :).

Azarien napisał(a)

Bez volatile teoretycznie zmienna progress może skoczyć z 0 na 100 dopiero pod koniec obliczeń.

A to raczej nie jest kwestia współbieżności, co raczej faktu, że to volatile w Twoim kodzie uwala kompilatorowi parę optymalizacji. Moim zdaniem problem w kodzie powyżej jest wyłącznie na poziomie optymalizacji funkcji thr1, gdzie kompilator może stwierdzić "nic nie robię z progress w pętli - rozwinę pętlę i podstawię za progress 100". Thr2 zawsze będzie ładowało wartość ze zmiennej globalnej niezależnie od volatile.

edytowany 1x, ostatnio: kapojot

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.