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:prawie 20 lat
  • Ostatnio:8 minut
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:16 dni
  • 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:prawie 20 lat
  • Ostatnio:8 minut
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:16 dni
  • 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:prawie 20 lat
  • Ostatnio:8 minut
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:około 4 godziny
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:ponad 13 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:16 dni
  • 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:ponad 8 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:około 4 godziny
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:około 4 godziny
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:około 4 godziny
0
Kopiuj
        sum += 1;


        sum -= 1;

Źle i dobrze wiesz dlaczego źle.

edytowany 3x, ostatnio: Azarien
mlyszczek
  • Rejestracja:ponad 8 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:około 4 godziny
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:ponad 8 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
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)