Friday Mar 20, 2009

Volatile and Mutexes

A rough-and-ready guide to when to use the volatile keyword and when to use mutexes.

When you declare a variable to be volatile it ensures that the compiler always loads that variable from memory and immediately stores it back to memory after any operation on it.

For example:

int flag;

while (flag){}

In the absence of the volatile keyword the compiler will optimise this to:

if (!flag) while (1) {}

[If the flag is not zero to start with then the compiler assumes that there's nothing that can make it zero, so there is no exit condition on the loop.]

Not all shared data needs to be declared volatile. Only if you want one thread to see the effect of another thread.

[Example, if one thread populates a buffer, and another thread will later read from that buffer, then you don't need to declare the contents of the buffer as volatile. The important word being later, if you expect the two threads to access the buffer at the same time, then you would probably need the volatile keyword]

Mutexes are there to ensure exclusive access:

You will typically need to use them if you are updating a variable, or if you are performing a complex operation that should appear 'atomic'

For example:

volatile int total;

mutex_lock();
total+=5;
mutex_unlock();

You need to do this to avoid a data race, where another thread could also be updating total:

Here's the situation without the mutex:

Thread 1    Thread 2
Read total  Read total
Add 5       Add 5
Write total Write total

So total would be incremented by 5 rather than 10.

An example of a complex operation would be:

mutex_lock();
My_account = My_account - bill;
Their_account = Their_account + bill;
mutex_unlock();

You could use two separate mutexes, but then there would be a state where the amount bill would have been removed from My_account, but not yet placed into Their_account (this may or may not be a problem).

Tuesday Jun 12, 2007

Volatile

The keyword volatile should be used in situations where an item of data is shared between multiple threads. An example of this is the code:

int ready;
...
  while (ready);
...

This code gets compiled into:

/\* 0x0004            \*/         ld      [%o5+%lo(ready)],%o5
/\* 0x0008            \*/         cmp     %o5,0
/\* 0x000c            \*/         be,pn   %icc,.L77000022
/\* 0x0010            \*/         nop

                        .L77000017:
/\* 0x0014          5 \*/         ba      .L77000017
/\* 0x0018            \*/         nop

                        .L77000022:
/\* 0x001c          5 \*/         retl    ! Result =
/\* 0x0020            \*/         nop

Compare this to the situation where the variable is declared as volatile:

                        .L900000106:
/\* 0x0018          5 \*/         cmp     %o5,0
/\* 0x001c            \*/         be,pn   %icc,.L77000022
/\* 0x0020            \*/         nop


                        .L77000017:
/\* 0x0024          5 \*/         ld      [%o2],%o3 ! volatile
/\* 0x0028            \*/         cmp     %o3,0
/\* 0x002c            \*/         bne,a,pt        %icc,.L900000106
/\* 0x0030            \*/         ld      [%o2],%o5 ! volatile

                        .L77000022:
/\* 0x0034          5 \*/         retl    ! Result =
/\* 0x0038            \*/         nop

The endless loop has been removed, the variable is now polled.

The situation is interesting when using a pointer:

volatile int \* v1;
int \* volatile v2;

...
  while (\*v1);
  while (\*v2);
...

The two pointers are treated differently

                        .L900000109:
/\* 0x001c          6 \*/         cmp     %o5,0
/\* 0x0020            \*/         be,pn   %icc,.L900000110
/\* 0x0024          0 \*/         sethi   %hi(v2),%g5

                        .L77000026:
/\* 0x0028          6 \*/         ld      [%o4],%g1 ! volatile
/\* 0x002c            \*/         cmp     %g1,0
/\* 0x0030            \*/         bne,a,pt        %icc,.L900000109
/\* 0x0034            \*/         ld      [%o4],%o5 ! volatile
...

                        .L900000108:
/\* 0x0054          7 \*/         ld      [%o5],%o4
/\* 0x0058            \*/         cmp     %o4,0
/\* 0x005c            \*/         be,pn   %icc,.L77000039
/\* 0x0060            \*/         nop

                        .L77000034:
/\* 0x0064          7 \*/         ld      [%g2],%o1 ! volatile
/\* 0x0068            \*/         ld      [%o1],%o0
/\* 0x006c            \*/         cmp     %o0,0
/\* 0x0070            \*/         bne,a,pt        %icc,.L900000108
/\* 0x0074            \*/         ld      [%g2],%o5 ! volatile
...

v1 is a pointer to a volatile int, whereas v2 is a volatile pointer to an int. So v2 requires both the pointer and the data it points to to be reloaded, v1 only reloads the data being pointed to.

About

Darryl Gove is a senior engineer in the Solaris Studio team, working on optimising applications and benchmarks for current and future processors. He is also the author of the books:
Multicore Application Programming
Solaris Application Programming
The Developer's Edge

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
5
6
8
9
10
12
13
14
15
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today
Bookmarks
The Developer's Edge
Solaris Application Programming
Publications
Webcasts
Presentations
OpenSPARC Book
Multicore Application Programming
Docs