|
List Info
Thread: Advice on lock free programming
|
|
| Advice on lock free programming |

|
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-devel lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jackit-dev
el
|
|
| Re: Advice on lock free programming |
  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-devel lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jackit-dev
el
|
|
| Re: Advice on lock free programming |

|
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-devel lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jackit-dev
el
|
|
| Re: Advice on lock free programming |
  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-devel lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jackit-dev
el
|
|
| Re: Advice on lock free programming |
  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-devel lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jackit-dev
el
|
|
[1-5]
|
|