List Info

Thread: Advice on lock free programming




Advice on lock free programming
user name
2007-02-05 04:48:53
Hi all, especially the lock-free programming guru's.

We need to have a way to update a set of variables
atomically. Currently 
we are using a mutex based approach, but of course we want
to go lock-free.

 > Pieter Palmers wrote:
 >> Jonathan Woithe wrote:
 >> Indeed.  Maybe we'll need to move to some sort of
double-buffering.
 >> We have two sets of the critical variables.  One
set is the "live"
 >> set (used by the iso packet handler reader) and
one "shadow" set used
 >> for updating by the transmit SP.  When an update
is needed the shadow
 >> set is changed and when complete the shadow and
live sets are changed
 >> over.  This means that any readers which happen to
read the variables
 >> during an update will always get a consistent
answer since at any one
 >> time the variables in the live set are guaranteed
to be consistent
 >> with each other.
 >
 > This is the start of the a lock free solution for
this, but there are
 > some issues. The most important one being that these
sets are also
 > updated in the packet handler routine, having high
priority.
 > So how do you prevent the switch to occur when the
client thread is
 > accessing it (without mutexes)? If we would implement
this with
 > mutexes the effect would be that you shorten the
protected section,
 > but I don't think that is significant to the mutex
overhead.
 >
 > A classic solution is to use more than double
buffering, e.g. cycling
 > through a list of 10 pointers, making the chance of
simultaneous
 > access smaller. I don't like chances.
 >
 > What might work is the following approach:
 > Suppose we have a list of e.g. 10 pointers, and 2
lock-free
 > ringbuffers: rb_valid and rb_invalid. rb_valid
contains pointers to
 > structs that still have to be read, rb_invalid
contains pointers that
 > can be written to.
 >
 > A reading thread would empty the rb_valid until only
one pointer
 > remains in the buffer. It would then read from this
last pointer. The
 > pointers that are read from rb_valid by the reader are
written back
 > into rb_invalid. A reading thread should always leave
1 pointer in the
 > buffer, so that there is always a valid value
available.
 >
 > When updating, the writing thread would read one
pointer from
 > rb_invalid, modify it's values, and write it to
rb_valid.
 >

I'd like to have the expert's comments on the solution I
propose here. 
Is it ok? are there better solutions? I still have to read
up on the 
subject... (e.g. Stephane's papers)

Thanks,

Pieter

------------------------------------------------------------
-------------
Using Tomcat but need to do more? Need to support web
services, security?
Get stuff done quickly with pre-integrated technology to
make your job easier.
Download IBM WebSphere Application Server v.1.0.1 based on
Apache Geronimo
http://sel.as-us.falkag.net/
sel?cmd=lnk&kid=120709&bid=263057&dat=121642

_______________________________________________
Jackit-devel mailing list
Jackit-devellists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jackit-dev
el

Re: Advice on lock free programming
country flaguser name
France
2007-02-05 04:58:40
Le 5 févr. 07 à 11:48, Pieter Palmers a écrit :

> Hi all, especially the lock-free programming guru's.
>
> We need to have a way to update a set of variables
atomically.  
> Currently
> we are using a mutex based approach, but of course we
want to go  
> lock-free.
>
>> Pieter Palmers wrote:
>>> Jonathan Woithe wrote:
>>> Indeed.  Maybe we'll need to move to some sort
of double-buffering.
>>> We have two sets of the critical variables. 
One set is the "live"
>>> set (used by the iso packet handler reader) and
one "shadow" set  
>>> used
>>> for updating by the transmit SP.  When an
update is needed the  
>>> shadow
>>> set is changed and when complete the shadow and
live sets are  
>>> changed
>>> over.  This means that any readers which happen
to read the  
>>> variables
>>> during an update will always get a consistent
answer since at any  
>>> one
>>> time the variables in the live set are
guaranteed to be consistent
>>> with each other.
>>
>> This is the start of the a lock free solution for
this, but there are
>> some issues. The most important one being that
these sets are also
>> updated in the packet handler routine, having high
priority.
>> So how do you prevent the switch to occur when the
client thread is
>> accessing it (without mutexes)? If we would
implement this with
>> mutexes the effect would be that you shorten the
protected section,
>> but I don't think that is significant to the mutex
overhead.
>>
>> A classic solution is to use more than double
buffering, e.g. cycling
>> through a list of 10 pointers, making the chance of
simultaneous
>> access smaller. I don't like chances.
>>
>> What might work is the following approach:
>> Suppose we have a list of e.g. 10 pointers, and 2
lock-free
>> ringbuffers: rb_valid and rb_invalid. rb_valid
contains pointers to
>> structs that still have to be read, rb_invalid
contains pointers that
>> can be written to.
>>
>> A reading thread would empty the rb_valid until
only one pointer
>> remains in the buffer. It would then read from this
last pointer. The
>> pointers that are read from rb_valid by the reader
are written back
>> into rb_invalid. A reading thread should always
leave 1 pointer in  
>> the
>> buffer, so that there is always a valid value
available.
>>
>> When updating, the writing thread would read one
pointer from
>> rb_invalid, modify it's values, and write it to
rb_valid.
>>
>
> I'd like to have the expert's comments on the solution
I propose here.
> Is it ok? are there better solutions?

Hard to say. What is the context of the concurent access?
(how many  
readers/writer threads...)

> I still have to read up on the
> subject... (e.g. Stephane's papers)
>
> Thanks,

The jackdmp approach is more like the 
"double-buffering" solution  
with a "current "state and a  "next"
state, and an atomic switch  
between the 2. Of course they are a lot  conditions fo
follow to have  
this kind of solution to work.
Stephane


------------------------------------------------------------
-------------
Using Tomcat but need to do more? Need to support web
services, security?
Get stuff done quickly with pre-integrated technology to
make your job easier.
Download IBM WebSphere Application Server v.1.0.1 based on
Apache Geronimo
http://sel.as-us.falkag.net/
sel?cmd=lnk&kid=120709&bid=263057&dat=121642

_______________________________________________
Jackit-devel mailing list
Jackit-devellists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jackit-dev
el

Re: Advice on lock free programming
user name
2007-02-05 07:27:59
Stéphane Letz wrote:
> Le 5 févr. 07 à 11:48, Pieter Palmers a écrit :
> 
>> Hi all, especially the lock-free programming
guru's.
>>
>> We need to have a way to update a set of variables
atomically.  
>> Currently
>> we are using a mutex based approach, but of course
we want to go  
>> lock-free.
>>
>>> Pieter Palmers wrote:
>>>> Jonathan Woithe wrote:
>>>> Indeed.  Maybe we'll need to move to some
sort of double-buffering.
>>>> We have two sets of the critical variables.
 One set is the "live"
>>>> set (used by the iso packet handler reader)
and one "shadow" set  
>>>> used
>>>> for updating by the transmit SP.  When an
update is needed the  
>>>> shadow
>>>> set is changed and when complete the shadow
and live sets are  
>>>> changed
>>>> over.  This means that any readers which
happen to read the  
>>>> variables
>>>> during an update will always get a
consistent answer since at any  
>>>> one
>>>> time the variables in the live set are
guaranteed to be consistent
>>>> with each other.
>>> This is the start of the a lock free solution
for this, but there are
>>> some issues. The most important one being that
these sets are also
>>> updated in the packet handler routine, having
high priority.
>>> So how do you prevent the switch to occur when
the client thread is
>>> accessing it (without mutexes)? If we would
implement this with
>>> mutexes the effect would be that you shorten
the protected section,
>>> but I don't think that is significant to the
mutex overhead.
>>>
>>> A classic solution is to use more than double
buffering, e.g. cycling
>>> through a list of 10 pointers, making the
chance of simultaneous
>>> access smaller. I don't like chances.
>>>
>>> What might work is the following approach:
>>> Suppose we have a list of e.g. 10 pointers, and
2 lock-free
>>> ringbuffers: rb_valid and rb_invalid. rb_valid
contains pointers to
>>> structs that still have to be read, rb_invalid
contains pointers that
>>> can be written to.
>>>
>>> A reading thread would empty the rb_valid until
only one pointer
>>> remains in the buffer. It would then read from
this last pointer. The
>>> pointers that are read from rb_valid by the
reader are written back
>>> into rb_invalid. A reading thread should always
leave 1 pointer in  
>>> the
>>> buffer, so that there is always a valid value
available.
>>>
>>> When updating, the writing thread would read
one pointer from
>>> rb_invalid, modify it's values, and write it to
rb_valid.
>>>
>> I'd like to have the expert's comments on the
solution I propose here.
>> Is it ok? are there better solutions?
> 
> Hard to say. What is the context of the concurent
access? (how many  
> readers/writer threads...)
In our case there are only 2 threads accessing the
structure, but both 
are readers & writers at once. They both perform
something like: read 
the data, determine if we want to update, calculate update,
do update.

The context is a timestamped frame ringbuffer (fifo):
1) lockfree ringbuffer for the frames
2) the 'bookkeeping' structure:
    - framecounter to keep track of the buffer fill [*]
    - buffer head timestamp (= timestamp of first frame in
fifo)
    - buffer tail timestamp (= timestamp of last frame in
fifo)

[*] we keep a separate ringbuffer fill value because that
allows the 
ringbuffer read/write to be performed outside the protected
section. We 
make sure that the framecounter is always <= than the
actual buffer fill 
by only updating it after a read/write has finished.

The way this is used is as follows (simplified):
There are two threads, one client thread and one iso handler
thread.
The client thread waits for a period to be ready, then reads
a period 
from the receive buffer, processes it and writes it to the
transmit 
buffer. (i.e. jackd process cycle for a backend).

The ISO thread has two subactions:
- ISO receive: a ISO packet is received by the firewire
controller. it 
is processed to extract the frames, and these are written to
the receive 
buffer.
- ISO send: a packet should be queued for transmission, so
the necessary 
amount of frames is read from the xmit buffer and these are
transformed 
into the ISO packet.

All received packets have a timestamp, and all transmitted
ones should 
get one. That is why the buffer is 'timestamped': if you put
a frame in 
the buffer, you should update the tail timestamp with the
timestamp of 
that frame. If you read a frame from the buffer, you should
update the 
head timestamp. At the same time you should update the
framecounter, 
because that will allow you (together with the
'rate'=time/frame) to 
calculate the timestamp for any other frame in the buffer:
TSTAMP(x)=Ttail - x * rate

(note that due to simplification it seems as if the head
timestamp is 
redundant, but it isn't)

The buffer head timestamp is calculated from the tail
timestamp as follows:
TSTAMP(head)=Ttail - framecounter * rate

The update of Ttail & framecounter should occur
atomically, because 
otherwise the calculation might go wrong. This is not good,
as the head 
timestamp determines the 'buffer ready' signaling time (for
receive) and 
  is equal to the next packet's timestamp for transmit.
(modulo some 
details)

Another important thing is that the two threads run at a
different rate:
The ISO handlers run at packet rate, being 8000times/sec.
However not 
all packets contain frames. The number of frames per packet
is fixed (=8 
for lower samplerates), meaning that the receive buffer is
'fed' with 8 
frames at a time, and the transmit buffer is consumed with 8
frames at a 
time.
The client thread runs at period rate, i.e. a order of
magnitude slower.

It comes down to:
* writing is done by one thread only: the ISO thread for
receive, the 
client thread for transmit.
* both threads need read access to a consistent variable
set.

However: I think the algorithm described above works no
matter how many 
writer and reader threads, as long as you don't have to
preserve the 
order of the sets.

The only weakness is determining whether the rb_valid buffer
contains 
one value or not. Suppose there are 2 pointers in there, and
two threads 
(A & B) are accessing it:
A) xa = get_fill(rb_valid) => xa=2
B) xb = get_fill(rb_valid) => xb=2
A) if (xa>1)
      ptr=read(rb_valid);   => fill is now 1
      write(rb_invalid, ptr);
B) if (xb>1)
      ptr=read(rb_valid);   => fill is now 0, error!
      write(rb_invalid, ptr);

The solution to this in our case would be to have the writer
thread 
advance the rb_valid ringbuffer, and have the reader threads
always read 
the buffer head value.

A more generic solution might be:

  1: keep_trying=true; // we need one iteration at least
  2: while (keep_trying) {
  3:   ptr=read(rb_valid);
  4:   x=get_fill(rb_valid);

  5:   if(ptr==0) {

         // failed to read, this means that
         // the (an) other thread is between line 3
         // and line 8: it has emptied the
         // buffer and it will rewrite the value
         // later on. We keep trying until the other
         // thread succeeds

  6:   } else {
  7:     if(x==0) {

           // the read succeeded, but we emptied the
           // buffer, so we have to 'undo' the read.
           // the pointer we have is valid though.
  8:       write_to_front(rb_valid, ptr);

           // at this point we can choose: either use
           // this pointer and exit the loop, or
           // run the loop again and see if we really
           // have the first one (another thread might
           // write_to_front too.

  9:     } else if (x==1) {
           // the read succeeded, and we got (most likely)
           // the first value in the buffer
           keep_trying=false;

10:     } else {
           // we didn't take the last value in the
           // buffer, so we can invalidate it, and
           // we have to retry
11:       write(rb_invalid, ptr);

12:     }

13:   }

14: }


At first sight, the only downside is that this can cause
reordering of 
the pointers because:
buffer = [p1, p2]
A) read      => get p1, buffer = [p2]
B) read      => get p2, buffer = []
A) get_fill  => 0
B) get_fill  => 0
A) put_back  => put p1, buffer = [p1]
B) put_back  => put p2, buffer = [p2,p1]

meaning that p2 is 'elected' as the most recent value.

If we could combine line 1 & 2 into one atomic
operation, the 'most 
likely' can be eliminated, and the reordering problem
doesn't exist. And 
so we are back where we started...

would this be solved with something like:

while(keep_trying)
   if(lock==0)
     atomic_inc(lock)

     if(lock==1)
       // do read & get_fill

       atomic_dec(lock)
       keep_trying=false

     else

       // an other thread entered this too
       // try again.
       atomic_dec(lock)


but then you'd have to be careful that the busy wait loop
doesn't lock 
the system (priority inversion can be an issue here too I
guess). This 
looks a lot like a mutex based implementation.

OTOH: "do read & get_fill" could be replaced
by "do anything you want 
with the data structure"

> 
>> I still have to read up on the
>> subject... (e.g. Stephane's papers)
>>
>> Thanks,
> 
> The jackdmp approach is more like the 
"double-buffering" solution  
> with a "current "state and a 
"next" state, and an atomic switch  
> between the 2. Of course they are a lot  conditions fo
follow to have  
> this kind of solution to work.

e.g. the state update thread has to be ready with updating
the state 
before the process thread switches the two. How do you solve
that? The 
solution to that is the solution to the problems of the
stuff I propose 
above.

Greets,

Pieter

------------------------------------------------------------
-------------
Using Tomcat but need to do more? Need to support web
services, security?
Get stuff done quickly with pre-integrated technology to
make your job easier.
Download IBM WebSphere Application Server v.1.0.1 based on
Apache Geronimo
http://sel.as-us.falkag.net/
sel?cmd=lnk&kid=120709&bid=263057&dat=121642

_______________________________________________
Jackit-devel mailing list
Jackit-devellists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jackit-dev
el

Re: Advice on lock free programming
country flaguser name
France
2007-02-05 10:18:19
Le 5 févr. 07 à 14:27, Pieter Palmers a écrit :

> Stéphane Letz wrote:
>> Le 5 févr. 07 à 11:48, Pieter Palmers a écrit :
>>
>>> Hi all, especially the lock-free programming
guru's.
>>>
>>> We need to have a way to update a set of
variables atomically.
>>> Currently
>>> we are using a mutex based approach, but of
course we want to go
>>> lock-free.
>>>
>>>> Pieter Palmers wrote:
>>>>> Jonathan Woithe wrote:
>>>>> Indeed.  Maybe we'll need to move to
some sort of double- 
>>>>> buffering.
>>>>> We have two sets of the critical
variables.  One set is the "live"
>>>>> set (used by the iso packet handler
reader) and one "shadow" set
>>>>> used
>>>>> for updating by the transmit SP.  When
an update is needed the
>>>>> shadow
>>>>> set is changed and when complete the
shadow and live sets are
>>>>> changed
>>>>> over.  This means that any readers
which happen to read the
>>>>> variables
>>>>> during an update will always get a
consistent answer since at any
>>>>> one
>>>>> time the variables in the live set are
guaranteed to be consistent
>>>>> with each other.
>>>> This is the start of the a lock free
solution for this, but  
>>>> there are
>>>> some issues. The most important one being
that these sets are also
>>>> updated in the packet handler routine,
having high priority.
>>>> So how do you prevent the switch to occur
when the client thread is
>>>> accessing it (without mutexes)? If we would
implement this with
>>>> mutexes the effect would be that you
shorten the protected section,
>>>> but I don't think that is significant to
the mutex overhead.
>>>>
>>>> A classic solution is to use more than
double buffering, e.g.  
>>>> cycling
>>>> through a list of 10 pointers, making the
chance of simultaneous
>>>> access smaller. I don't like chances.
>>>>
>>>> What might work is the following approach:
>>>> Suppose we have a list of e.g. 10 pointers,
and 2 lock-free
>>>> ringbuffers: rb_valid and rb_invalid.
rb_valid contains pointers to
>>>> structs that still have to be read,
rb_invalid contains pointers  
>>>> that
>>>> can be written to.
>>>>
>>>> A reading thread would empty the rb_valid
until only one pointer
>>>> remains in the buffer. It would then read
from this last  
>>>> pointer. The
>>>> pointers that are read from rb_valid by the
reader are written back
>>>> into rb_invalid. A reading thread should
always leave 1 pointer in
>>>> the
>>>> buffer, so that there is always a valid
value available.
>>>>
>>>> When updating, the writing thread would
read one pointer from
>>>> rb_invalid, modify it's values, and write
it to rb_valid.
>>>>
>>> I'd like to have the expert's comments on the
solution I propose  
>>> here.
>>> Is it ok? are there better solutions?
>>
>> Hard to say. What is the context of the concurent
access? (how many
>> readers/writer threads...)
> In our case there are only 2 threads accessing the
structure, but both
> are readers & writers at once. They both perform
something like: read
> the data, determine if we want to update, calculate
update, do update.
>
> The context is a timestamped frame ringbuffer (fifo):
> 1) lockfree ringbuffer for the frames
> 2) the 'bookkeeping' structure:
>     - framecounter to keep track of the buffer fill
[*]
>     - buffer head timestamp (= timestamp of first frame
in fifo)
>     - buffer tail timestamp (= timestamp of last frame
in fifo)
>
> [*] we keep a separate ringbuffer fill value because
that allows the
> ringbuffer read/write to be performed outside the
protected  
> section. We
> make sure that the framecounter is always <= than
the actual buffer  
> fill
> by only updating it after a read/write has finished.
>
> The way this is used is as follows (simplified):
> There are two threads, one client thread and one iso
handler thread.
> The client thread waits for a period to be ready, then
reads a period
> from the receive buffer, processes it and writes it to
the transmit
> buffer. (i.e. jackd process cycle for a backend).
>
> The ISO thread has two subactions:
> - ISO receive: a ISO packet is received by the firewire
controller. it
> is processed to extract the frames, and these are
written to the  
> receive
> buffer.
> - ISO send: a packet should be queued for transmission,
so the  
> necessary
> amount of frames is read from the xmit buffer and these
are  
> transformed
> into the ISO packet.
>
> All received packets have a timestamp, and all
transmitted ones should
> get one. That is why the buffer is 'timestamped': if
you put a  
> frame in
> the buffer, you should update the tail timestamp with
the timestamp of
> that frame. If you read a frame from the buffer, you
should update the
> head timestamp. At the same time you should update the
framecounter,
> because that will allow you (together with the
'rate'=time/frame) to
> calculate the timestamp for any other frame in the
buffer:
> TSTAMP(x)=Ttail - x * rate
>
> (note that due to simplification it seems as if the
head timestamp is
> redundant, but it isn't)
>
> The buffer head timestamp is calculated from the tail
timestamp as  
> follows:
> TSTAMP(head)=Ttail - framecounter * rate
>
> The update of Ttail & framecounter should occur
atomically, because
> otherwise the calculation might go wrong. This is not
good, as the  
> head
> timestamp determines the 'buffer ready' signaling time
(for  
> receive) and
>   is equal to the next packet's timestamp for transmit.
(modulo some
> details)
>
> Another important thing is that the two threads run at
a different  
> rate:
> The ISO handlers run at packet rate, being
8000times/sec. However not
> all packets contain frames. The number of frames per
packet is  
> fixed (=8
> for lower samplerates), meaning that the receive buffer
is 'fed'  
> with 8
> frames at a time, and the transmit buffer is consumed
with 8 frames  
> at a
> time.
> The client thread runs at period rate, i.e. a order of
magnitude  
> slower.
>
> It comes down to:
> * writing is done by one thread only: the ISO thread
for receive, the
> client thread for transmit.
> * both threads need read access to a consistent
variable set.
>
> However: I think the algorithm described above works no
matter how  
> many
> writer and reader threads, as long as you don't have to
preserve the
> order of the sets.
>
> The only weakness is determining whether the rb_valid
buffer contains
> one value or not. Suppose there are 2 pointers in
there, and two  
> threads
> (A & B) are accessing it:
> A) xa = get_fill(rb_valid) => xa=2
> B) xb = get_fill(rb_valid) => xb=2
> A) if (xa>1)
>       ptr=read(rb_valid);   => fill is now 1
>       write(rb_invalid, ptr);
> B) if (xb>1)
>       ptr=read(rb_valid);   => fill is now 0,
error!
>       write(rb_invalid, ptr);
>
> The solution to this in our case would be to have the
writer thread
> advance the rb_valid ringbuffer, and have the reader
threads always  
> read
> the buffer head value.
>
> A more generic solution might be:
>
>   1: keep_trying=true; // we need one iteration at
least
>   2: while (keep_trying) {
>   3:   ptr=read(rb_valid);
>   4:   x=get_fill(rb_valid);
>
>   5:   if(ptr==0) {
>
>          // failed to read, this means that
>          // the (an) other thread is between line 3
>          // and line 8: it has emptied the
>          // buffer and it will rewrite the value
>          // later on. We keep trying until the other
>          // thread succeeds
>
>   6:   } else {
>   7:     if(x==0) {
>
>            // the read succeeded, but we emptied the
>            // buffer, so we have to 'undo' the read.
>            // the pointer we have is valid though.
>   8:       write_to_front(rb_valid, ptr);
>
>            // at this point we can choose: either use
>            // this pointer and exit the loop, or
>            // run the loop again and see if we really
>            // have the first one (another thread might
>            // write_to_front too.
>
>   9:     } else if (x==1) {
>            // the read succeeded, and we got (most
likely)
>            // the first value in the buffer
>            keep_trying=false;
>
> 10:     } else {
>            // we didn't take the last value in the
>            // buffer, so we can invalidate it, and
>            // we have to retry
> 11:       write(rb_invalid, ptr);
>
> 12:     }
>
> 13:   }
>
> 14: }
>
>
> At first sight, the only downside is that this can
cause reordering of
> the pointers because:
> buffer = [p1, p2]
> A) read      => get p1, buffer = [p2]
> B) read      => get p2, buffer = []
> A) get_fill  => 0
> B) get_fill  => 0
> A) put_back  => put p1, buffer = [p1]
> B) put_back  => put p2, buffer = [p2,p1]
>
> meaning that p2 is 'elected' as the most recent value.
>
> If we could combine line 1 & 2 into one atomic
operation, the 'most
> likely' can be eliminated, and the reordering problem
doesn't  
> exist. And
> so we are back where we started...
>
> would this be solved with something like:
>
> while(keep_trying)
>    if(lock==0)
>      atomic_inc(lock)
>
>      if(lock==1)
>        // do read & get_fill
>
>        atomic_dec(lock)
>        keep_trying=false
>
>      else
>
>        // an other thread entered this too
>        // try again.
>        atomic_dec(lock)
>
>
> but then you'd have to be careful that the busy wait
loop doesn't lock
> the system (priority inversion can be an issue here too
I guess). This
> looks a lot like a mutex based implementation.
>
> OTOH: "do read & get_fill" could be
replaced by "do anything you want
> with the data structure"
>
>>
>>> I still have to read up on the
>>> subject... (e.g. Stephane's papers)
>>>
>>> Thanks,
>>
>> The jackdmp approach is more like the 
"double-buffering" solution
>> with a "current "state and a 
"next" state, and an atomic switch
>> between the 2. Of course they are a lot  conditions
fo follow to have
>> this kind of solution to work.
>
> e.g. the state update thread has to be ready with
updating the state
> before the process thread switches the two. How do you
solve that? The
> solution to that is the solution to the problems of the
stuff I  
> propose
> above.
>
> Greets,
>
> Pieter
>

In jackdmp, there is a unique update thread and several
readers  
threads, and only one of them (the server audio thread) does
the  
state switch. If the RT thread tries to switch the states
while a  
write operation is occuring, the switch operation fails and
the RT  
thread still see the same state.
This is a slight variation of this pattern use for Transport
 
management (defined in JackAtomicArrayState.h). The idea is
to  
possibly have several new state being written, and one of
them will  
be taken as the new current state.

I cannot really answer concerning your proposed method, a
bit hard to  
follow while doing others things... ((-:  I need to think
about to  
see of the same "multiple next state" pattern
could be used.

Stephane




------------------------------------------------------------
-------------
Using Tomcat but need to do more? Need to support web
services, security?
Get stuff done quickly with pre-integrated technology to
make your job easier.
Download IBM WebSphere Application Server v.1.0.1 based on
Apache Geronimo
http://sel.as-us.falkag.net/
sel?cmd=lnk&kid=120709&bid=263057&dat=121642

_______________________________________________
Jackit-devel mailing list
Jackit-devellists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jackit-dev
el

Re: Advice on lock free programming
country flaguser name
Belgium
2007-02-07 01:22:20
Stéphane Letz wrote:
>> .
>>
>> The way this is used is as follows (simplified):
>> There are two threads, one client thread and one
iso handler thread.
>> The client thread waits for a period to be ready,
then reads a period
>> from the receive buffer, processes it and writes it
to the transmit
>> buffer. (i.e. jackd process cycle for a backend).
>>
>> The ISO thread has two subactions:
>> - ISO receive: a ISO packet is received by the
firewire controller. it
>> is processed to extract the frames, and these are
written to the receive
>> buffer.
>> - ISO send: a packet should be queued for
transmission, so the necessary
>> amount of frames is read from the xmit buffer and
these are transformed
>> into the ISO packet.
>>
> 
> Pieter,
> 
> It would help if you could provide a kind of simplified
implementation 
> of what does the client thread and the ISO thread. The
I can try to see 
> if the model used in jackdmp could be adapted for this
situation.
here we go, in some pseudo code:

// simplified approach for only one receive and
// one transmit ISO stream

// isochronous thread
iso_thread() {
   // wait until data is available from the 1394 stack
   poll(ReceiveStream, TransmitStream)

   // get all incoming data
   ReceiveStream.iterate()

   // get all outgoing data
   TransmitStream.iterate()
}

// the iterate function makes libraw1394 call
// the registered callback function for each
// packet that is received (rcv stream) or is
// to be queued (xmit stream)

ReceiveStream.callback(packet *p) {
   this.ringbuffer_write(p->payload, p->nb_events)

   mutex_lock(this.lock)
     this.buffer_tail_timestamp=p->timestamp
     this.framecounter += p->nb_frames_in_payload
   mutex_unlock(this.lock)
}

TransmitStream.callback(packet *p) {
   p->nb_events=some_constant; // (=8 fs<=48000)

   p->payload=this.ringbuffer_read(p->nb_events)
   p->timestamp=this.buffer_head_timestamp

   mutex_lock(this.lock)
     this.buffer_head_timestamp +=
        time_per_frame * p->nb_events
     this.framecounter -= p->nb_frames_in_payload
   mutex_unlock(this.lock)
}

// client thread (= the jackd backend thread)
client_thread() {
   wait_for_period()

   timestamp_at_period=ReceiveStream.buffer_head_timestamp

   ReadFrames()
   process()
   WriteFrames()
}

ReadFrames() {
   ReceiveStream.ringbuffer_read(one_period_frames)

   mutex_lock(this.lock)
     ReceiveStream.buffer_head_timestamp +=
        time_per_frame * one_period_frames
     ReceiveStream.framecounter -=
p->nb_frames_in_payload
   mutex_unlock(this.lock)
}

WriteFrames() {
   TransmitStream.ringbuffer_write(one_period_frames)

   mutex_lock(this.lock)
     this.buffer_tail_timestamp=timestamp_at_period
     this.framecounter += one_period_frames
   mutex_unlock(this.lock)
}

> 
> Thanks
You are the one to be thanked!

Pieter

------------------------------------------------------------
-------------
Using Tomcat but need to do more? Need to support web
services, security?
Get stuff done quickly with pre-integrated technology to
make your job easier.
Download IBM WebSphere Application Server v.1.0.1 based on
Apache Geronimo
http://sel.as-us.falkag.net/
sel?cmd=lnk&kid=120709&bid=263057&dat=121642

_______________________________________________
Jackit-devel mailing list
Jackit-devellists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jackit-dev
el

[1-5]

about | contact  Other archives ( Real Estate discussion Medical topics )