Active object: Difference between revisions

→‎{{header|Crystal}}: i was made aware the previous example had potential memory corruption and thread safety issues
(→‎{{header|Python}}: port to python 3 as python 2 is eol)
(→‎{{header|Crystal}}: i was made aware the previous example had potential memory corruption and thread safety issues)
Line 417:
=={{header|Crystal}}==
{{trans|Python}}
Crystal currently runs all code in a single thread, so a trivial example wouldn't have any issues with thread safety. However, this behavior will likely change in the future. This example was written with that in mind, and is somewhat more complex to show better idioms and be future-proof.
<lang ruby>require "math"
require "time"
 
# this enum allows us to specify what type of message the proc_chan received.
# this trivial example only has one action, but more enum members can be added
# to update the proc, or take other actions
enum Action
Finished # we've waited long enough, and are asking for our result
# Update # potential member representing an update to the integrator function
end
 
class Integrator
Line 424 ⟶ 433:
getter s : Float64 = 0f64
 
# initialize our k function as a proc that takes a float and just returns 0
propertygetter k : Proc(Float64, Float64) = ->(t : Float64) { 0f64 }
# and just returns 0 (as a 64-bit float)
property k : Proc(Float64, Float64) = ->(t : Float64) { 0f64 }
 
# channels used for communicating with the main fiber
def initialize(@k, @interval = 1e-4)
@proc_chan : Channel(Tuple(Action, Proc(Float64, Float64)|Nil))
@result_chan : Channel(Float64)
 
def initialize(@k, @proc_chan, @result_chan, @interval = 1e-4)
# use a monotonic clock for accuracy
start = Time.monotonic.total_seconds
t0, k0 = 0f64, @k.call(0f64)
 
loop do
# spawn creates a new fiber.
# since the# fiberthis immediatelysleep sleeps,returns control is returned to the main codefiber. if the main fiber hasn't finished sleeping,
# control will be returned to this loop
# the main code then sleeps for two seconds, returning control to our state_clock fiber.
sleep interval.seconds
# when two seconds is up, this state_clock fiber will return control
# tocheck the mainchannel to codesee onif the nextfunction `sleephas interval.seconds`changed
self.check_channel()
spawn name: "state_clock" do
t1 = Time.monotonic.total_seconds - start
loop do
k1 = sleep interval@k.secondscall(t1)
@s += t1(k1 =+ Time.monotonic.total_secondsk0) * (t1 - startt0) / 2.0
t0, k1k0 = @k.call(t1), k1
end
@s += (k1 + k0) * (t1 - t0) / 2.0
end
t0, k0 = t1, k1
 
# check the proc_chan for messages, update the integrator function or send the result as needed
def check_channel
select
when message = @proc_chan.receive
action, new_k = message
case action
when Action::Finished
@result_chan.send @s
@k = new_k unless new_k.nil?
end
else
nil
end
end
end
 
# this channel allows us to update the integrator function,
ai = Integrator.new ->(t : Float64) { Math.sin(Math::PI * t) }
# and inform the integrator to send the result over the result channel
proc_chan = Channel(Tuple(Action, Proc(Float64, Float64)|Nil)).new
 
# channel used to return the result from the integrator
result_chan = Channel(Float64).new
 
# run everything in a new top-level fiber to avoid shared memory issues.
# since the fiber immediately sleeps, control is returned to the main code.
# the main code then sleeps for two seconds, returning control to our state_clock fiber.
# when two seconds is up, this state_clock fiber will return control
# to the main code on the next `sleep interval.seconds`
spawn name: "state_clock" do
ai = Integrator.new ->(t : Float64) { Math.sin(Math::PI * t) }, proc_chan, result_chan
end
 
sleep 2.seconds
aiproc_chan.k =send({Action::Finished, ->(t : Float64) { 0f64 }})
sleep 0.5.seconds
puts result_chan.receive</lang>
puts ai.s
</lang>
 
Output:
<pre>
-2.5475883655389925e-10
2.1379517395755217e-6
</pre>
 
Anonymous user