Synchronization with Atom and Async

11 views
Skip to first unread message

Arindam Mukherjee

unread,
Aug 2, 2016, 1:05:07 AM8/2/16
to Concurrent Ruby
I have a requirement where multiple futures will be updating a shared data structure, and this update needs to be synchronized. In some cases, this can happen asynchronously and in some cases this needs to happen synchronously.

Can I use Concurrent Agent and Concurrent Atom for the purpose?
Are Agent#send calls serialized with respect to other send calls on the same instance?
Atom#swap calls don't seem to be serialized. But is compare_and_set serialized - for any complex object that defines equality meaningful equality semantics?

Arindam

Chris Seaton

unread,
Aug 2, 2016, 6:41:53 AM8/2/16
to Arindam Mukherjee, Concurrent Ruby
On 2 Aug 2016, at 06:05, Arindam Mukherjee <arindam....@gmail.com> wrote:

I have a requirement where multiple futures will be updating a shared data structure, and this update needs to be synchronized. In some cases, this can happen asynchronously and in some cases this needs to happen synchronously.

In an ideal case I’d say that futures probably shouldn’t be updating shared state. I won’t tell you it’s wrong to do that but if it’s trivial to change it so they don’t then I’d do that.

I’m not sure what you mean to update shared state asynchronously though. Do you mean you want to start the update and not wait for it to finish? I’m not sure how that’s possible without starting a new concurrent task out of your future.

Can I use Concurrent Agent and Concurrent Atom for the purpose?

Yes.

Are Agent#send calls serialized with respect to other send calls on the same instance?

Yes. But I’d use the term ‘atomic’, so check we mean the same thing.

Atom#swap calls don't seem to be serialized. But is compare_and_set serialized - for any complex object that defines equality meaningful equality semantics?

Yes. But again I’d describe both swap and compare_and_set as atomic.


Arindam

--
You received this message because you are subscribed to the Google Groups "Concurrent Ruby" group.
To unsubscribe from this group and stop receiving emails from it, send an email to concurrent-ru...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Arindam Mukherjee

unread,
Aug 2, 2016, 9:30:24 AM8/2/16
to Chris Seaton, Concurrent Ruby
Thank you Chris. I am trying to understand the defined behaviour of
Atom. I have a couple of more questions in that direction.

My understanding of atomicity is that it is something innate in the
hardware, exposed by the processor architecture as instructions in the
instruction set. OS and languages then provide primitives that are
wrappers around these instructions. Such instructions typically apply
to integers or addresses. Are `swap` / `compare_and_set` atomic in
that sense?

In the case of Atom, my impression is that the block we pass to `swap`
is never run concurrently with another such block, for a given
instance of Atom. One block must complete before another one could
start. But if this block starts waiting on some input, or starts
sleeping, it seems that another block is scheduled to run even when
the first one's not done yet.

With that understanding, I wrote the following piece of code. It
updates a hash { seq_no: 0, steps: [] } concurrently a hundred times.
Each time, a thread increments the seq_no, and adds that number to the
steps array.

require 'concurrent'
require 'thread'
require 'thwait'

def atomic_hash(hash = nil)
puts "Starting in #{Thread.current}" # line A
if hash.nil?
puts "Done in #{Thread.current}" # line B
{ seq_no: 0, steps: [] }
else
# sleep rand * 5
seq = Concurrent::AtomicFixnum.new(hash[:seq_no])
hash[:seq_no] = seq.increment
hash[:steps] << seq.value
puts "Done in #{Thread.current}" # line C
hash
end
end

hash_atom = Concurrent::Atom.new(atomic_hash)
threads = []

100.times do |i|
threads << Thread.new do
hash_atom.swap(i) {|val, *args| atomic_hash(val) }
end
end

ThreadsWait.all_waits(*threads)

puts hash_atom.value

Here I expect the atomic_hash function passed to `swap` to not run
concurrently. But I occasionally see that there could be overlaps,
based on the "Starting ..." and "Done ..." messages, i.e. I would see
two "Starting ..." (line A) messages in immediate succession before
the corresponding "Done ..." messages appear (line B or line C).

Thanks,
Arindam

Chris Seaton

unread,
Aug 2, 2016, 9:55:11 AM8/2/16
to Arindam Mukherjee, Concurrent Ruby

> On 2 Aug 2016, at 14:30, Arindam Mukherjee <arindam....@gmail.com> wrote:
>
> Thank you Chris. I am trying to understand the defined behaviour of
> Atom. I have a couple of more questions in that direction.
>
> My understanding of atomicity is that it is something innate in the
> hardware, exposed by the processor architecture as instructions in the
> instruction set. OS and languages then provide primitives that are
> wrappers around these instructions. Such instructions typically apply
> to integers or addresses. Are `swap` / `compare_and_set` atomic in
> that sense?

Atomicity is a logical property so it can be provided by any level of abstraction. Processors provide atomic operations on virtual memory, but we can also provide our own atomicity on Ruby objects, and an application could provide atomicity on its own data structures above that.

Yes, swap and compare_and_set are atomic in that sense. They either happen or they do not.

> In the case of Atom, my impression is that the block we pass to `swap`
> is never run concurrently with another such block, for a given
> instance of Atom. One block must complete before another one could
> start.

All correct I think.

> But if this block starts waiting on some input, or starts
> sleeping, it seems that another block is scheduled to run even when
> the first one's not done yet.

Ah there is a slight confusion here then. The swapping itself is done atomically, but your block is not. Your block may be run multiple times, concurrently with other blocks, so it should be a pure operation, only worrying about its input and output, not performing side effects.
Yes I see what you were thinking now. Yes the block could run concurrently with others, and that is why you are seeing this. Your block should ideally not have these kind of side effects, and keep in mind your block could be run multiple times.

I would cautiously suggest that Atom may not be the best tool for the job then. If you want that block running only one at a time then you’ll need to use a lock (or maybe an Agent). Yes that’s going to reduce your parallelism, but if you need to prevent these blocks running concurrently then yeah we can’t also make them run in parallel.

Arindam Mukherjee

unread,
Aug 2, 2016, 10:19:08 AM8/2/16
to Chris Seaton, Concurrent Ruby
Good learning. I love this library but I end up creating a lot of
chatter on the list!

> Yes I see what you were thinking now. Yes the block could run concurrently with others, and that is why you are seeing this. Your block should ideally not have these kind of side effects, and keep in mind your block could be run multiple times.
>

Under what circumstances would the block be run multiple times?

> I would cautiously suggest that Atom may not be the best tool for the job then. If you want that block running only one at a time then you’ll need to use a lock (or maybe an Agent). Yes that’s going to reduce your parallelism, but if you need to prevent these blocks running concurrently then yeah we can’t also make them run in parallel.

Yes indeed, parallelism isn't something I expect to have here. The
update of the shared data structure is decidedly serialized. I shall
dig into Agent a little bit - simply because I prefer the abstraction
it provides over using a lock, and I'd prefer not to write my own
abstraction and spend time testing it. However, what I need really is
a __synchronous__ agent! Any suggestions?

Regards,
Arindam

Arindam Mukherjee

unread,
Aug 2, 2016, 12:28:27 PM8/2/16
to Chris Seaton, Concurrent Ruby

I am sorry, hit reply instead of reply all.

On Aug 2, 2016 21:55, "Chris Seaton" <chrisg...@gmail.com> wrote:

On 2 Aug 2016, at 17:04, Arindam Mukherjee <arindam....@gmail.com> wrote:


On Aug 2, 2016 20:21, "Chris Seaton" <chrisg...@gmail.com> wrote:


>
>
> > On 2 Aug 2016, at 15:19, Arindam Mukherjee <arindam....@gmail.com> wrote:
> >
> > Under what circumstances would the block be run multiple times?

> ...
>
>
> So the block can be re-run if someone else modifies it between your block starting and finishing.
>

Cool. So that's a CAS spin lock. When I do:

atom.swap {|val, *args| ...}

what's the old / reference value, which if changed results in a retry?


val is the current value at the time of the block starting. If there is a value older than that you aren’t told about it.

I didn’t mean to take this off list by the way.

>
> Have you tried just using a big lock?
>

Yes that's what I will use.

Reply all
Reply to author
Forward
0 new messages