Events
You are encouraged to solve this task according to the task description, using any language you may know.
Event is a synchronization object. An event has two states signaled and reset. A task may await for the event to enter the desired state, usually the signaled state. It is released once the state is entered. Releasing waiting tasks is called event notification. Programmatically controlled events can be set by a task into one of its states.
In concurrent programming event also refers to a notification that some state has been reached through an asynchronous activity. The source of the event can be:
- internal, from another task, programmatically;
- external, from the hardware, such as user input, timer, etc. Signaling an event from the hardware is accomplished by means of hardware interrupts.
Event is a low-level synchronization mechanism. It neither identify the state that caused it signaled, nor the source of, nor who is the subject of notification. Events augmented by data and/or publisher-subscriber schemes are often referred as messages, signals etc.
In the context of general programming event-driven architecture refers to a design that deploy events in order to synchronize tasks with the asynchronous activities they must be aware of. The opposite approach is polling sometimes called busy waiting, when the synchronization is achieved by an explicit periodic querying the state of the activity. As the name suggests busy waiting consumes system resources even when the external activity does not change its state.
Event-driven architectures are widely used in GUI design and SCADA systems. They are flexible and have relatively short response times. At the same time event-driven architectures suffer to the problems related to their unpredictability. They face race condition, deadlocking, live locks and priority inversion. For this reason real-time systems tend to polling schemes, trading performance for predictability in the worst case scenario.
Variants of events
Manual-reset event
This event changes its state by an explicit request of a task. I.e. once signaled it remains in this state until it will be explicitly reset.
Pulse event
A pulse event when signaled releases all tasks awaiting it and then is automatically reset.
Sample implementations / APIs
Show how a manual-reset event can be implemented in the language or else use an API to a library that provides events. Write a program that waits 1s and then signals the event to a task waiting for the event.
Ada
Ada provides higher-level concurrency primitives, which are complete in the sense that they also allow implementations of the lower-level ones, like event. Here is an implementation of the manual-reset event.
The event interface: <lang ada>protected type Event is
procedure Signal; procedure Reset; entry Wait;
private
Fired : Boolean := False;
end Event;</lang> The event implementation: <lang ada>protected body Event is
procedure Signal is begin Fired := True; end Signal; procedure Reset is begin Fired := False; end Reset; entry Wait when Fired is begin null; end Wait;
end Event;</lang> With the event defined above: <lang ada>with Ada.Text_IO; use Ada.Text_IO;
procedure Test_Events is
-- Place the event implementation here X : Event;
task A; task body A is begin Put_Line ("A is waiting for X"); X.Wait; Put_Line ("A received X"); end A;
begin
delay 1.0; Put_Line ("Signal X"); X.Signal;
end Test_Events;</lang> Sample output:
A is waiting for X Signal X A received X
AutoHotkey
<lang AutoHotkey>SetTimer, internal, 1000 Return
internal: ; fire on a timer
TrayTip, internal, internal event!`npress F2 for external event SetTimer, internal, off
Return
F2:: ; external event: fire on F2 key press
TrayTip, external, f2 key pressed
Return</lang>
C
Using pipe to communicate to fork
ed child. Since child will be blocking trying to read the other end of the pipe, this can be used for synchronization.
<lang c>#include <stdio.h>
- include <unistd.h>
int main() { int p[2]; pipe(p); if (fork()) { close(p[0]); sleep(1); write(p[1], p, 1); wait(0); } else { close(p[1]); read(p[0], p + 1, 1); puts("received signal from pipe"); } return 0; }</lang>
C#
<lang csharp>using System; using System.Timers;
class Program {
static void Main() { var timer = new Timer(1000); timer.Elapsed += new ElapsedEventHandler(OnElapsed); Console.WriteLine(DateTime.Now); timer.Start(); Console.ReadLine(); }
static void OnElapsed(object sender, ElapsedEventArgs eventArgs) { Console.WriteLine(eventArgs.SignalTime); ((Timer)sender).Stop(); }
}</lang> Sample output: <lang>10-11-2010 18:35:11 10-11-2010 18:35:12</lang>
Delphi
<lang Delphi>program Events;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes, Windows;
type
TWaitThread = class(TThread) private FEvent: THandle; public procedure Sync; procedure Execute; override; constructor Create(const aEvent: THandle); reintroduce; end;
{ TWaitThread }
constructor TWaitThread.Create(const aEvent: THandle); begin
inherited Create(False); FEvent := aEvent;
end;
procedure TWaitThread.Execute; var
res: Cardinal;
begin
res := WaitForSingleObject(FEvent, INFINITE); if res = 0 then Synchronize(Sync);
end;
procedure TWaitThread.Sync; begin
Writeln(DateTimeToStr(Now));
end;
var
event: THandle;
begin
Writeln(DateTimeToStr(Now)); event := CreateEvent(nil, False, False, 'Event'); with TWaitThread.Create(event) do try Sleep(1000); SetEvent(event) finally Free; end; Readln;
end. </lang> Sample output: <lang> 09.08.2011 0:27:43 09.08.2011 0:27:44 </lang>
E
<lang e>def makeEvent() {
def [var fired, var firer] := Ref.promise() def event { to signal() { firer.resolveRace(null) # all current and future wait()s will resolve } to reset() { if (firer.isDone()) { # ignore multiple resets. If we didn't, then # reset() wait() reset() signal() would never # resolve that wait(). # create all fresh state def [p, r] := Ref.promise() fired := p firer := r } } to wait() { return fired } } return event
}</lang>
The event object has this behavior: the return value of .wait()
will be resolved after the time of the earliest .signal()
for which there is no intervening .reset()
.
<lang e>def e := makeEvent()
{
when (e.wait()) -> { println("[2] Received event.") } println("[2] Waiting for event...")
}
{
timer.whenPast(timer.now() + 1000, def _() { println("[1] Signaling event.") e.signal() }) println("[1] Waiting 1 second...")
}</lang>
Go
Channel
A Go channel can represent an manual-reset event, as described by the task. The two states of signaled and reset correspond to the presence or absence of a value on the channel. The program signals by sending a value on the channel. The event is reset when the waiting task explicitly executes the channel receive operation, <-event. <lang go>package main
import (
"log" "os" "time"
)
func main() {
l := log.New(os.Stdout, "", log.Ltime | log.Lmicroseconds) l.Println("program start") event := make(chan int) go func() { l.Println("task start") <-event l.Println("event reset by task") }() l.Println("program sleeping") time.Sleep(1e9) l.Println("program signaling event") event <- 0 time.Sleep(1e8)
}</lang> Output:
01:27:21.862000 program start 01:27:21.862245 program sleeping 01:27:21.867269 task start 01:27:22.868294 program signaling event 01:27:22.868346 event reset by task
Semacquire
The functions semacquire and semrelease, in the runtime package of the standard library, provide a manual reset event capability exactly as described by the task.
Now, documentation states, "It is intended as a simple sleep primitive for use by the synchronization library and should not be used directly." Yet it is a well defined primitive and seems unlikely to be taken away in future releases. The warning can be considered discouragement from mucking with obscure low-level primitives when real-world problems can almost always be better expressed with higher-level language and library features. This RC task, however, not being a real-world problem, is solved most fundamentally with this pair of functions.
The event, as a object with two states, is now simply a uint32. The two states are 0 and 1 representing reset and signaled, respectively. Semrelease represents event notification, and increments s. Semacquire represents the explicit reset, decrementing s. <lang go>package main
import (
"log" "os" "runtime" "time"
)
func main() {
l := log.New(os.Stdout, "", log.Ltime | log.Lmicroseconds) l.Println("program start") var s uint32 go func() { l.Println("task start") runtime.Semacquire(&s) l.Println("event reset by task") }() l.Println("program sleeping") time.Sleep(1e9) l.Println("program signaling event") runtime.Semrelease(&s) time.Sleep(1e8)
}</lang>
Variants
The task description mentions a pulse event. Go has support for this in both the WaitGroup and Cond types in the sync package.
Also, in the time package, the Timer type offers different ways of starting a task after a specified delay, achieving the end result of this RC task, if not exposing all of the low-level synchronization details involved.
Haskell
<lang haskell>import Control.Concurrent (threadDelay, forkIO) import Control.Concurrent.SampleVar
-- An Event is defined as a SampleVar with no data. -- http://haskell.org/ghc/docs/latest/html/libraries/base/Control-Concurrent-SampleVar.html newtype Event = Event (SampleVar ())
newEvent = fmap Event (newEmptySampleVar) signalEvent (Event sv) = writeSampleVar sv () resetEvent (Event sv) = emptySampleVar sv waitEvent (Event sv) = readSampleVar sv</lang>
<lang haskell>main = do e <- newEvent
forkIO (waitTask e) putStrLn "[1] Waiting 1 second..." threadDelay 1000000 {- µs -} putStrLn "[1] Signaling event." signalEvent e threadDelay 1000000 {- µs -} -- defer program exit for reception
waitTask e = do putStrLn "[2] Waiting for event..."
waitEvent e putStrLn "[2] Received event."</lang>
Note: Because there is no serialization of the text output, there is a chance that it will appear interleaved.
JavaScript
An example using the YUI library:
<lang javascript>YUI().use('event-custom', function(Y) {
// add a custom event: Y.on('my:event', function () { alert("Event fired"); }); // fire the event after one second: setTimeout(function () { Y.fire('my:event'); }, 1000);
});</lang>
An example simulating DOM events:
<lang javascript>YUI().use('node-event-simulate', function(Y) {
// add a click event handler to a DOM node with id "button": Y.one("#button").on("click", function (e) { alert("Button clicked"); }); // simulate the click after one second: setTimeout(function () { Y.one("#button").simulate("click"); }, 1000);
});</lang>
Mathematica
Mathematica supports events from timers (via Pause[]), task schedule descriptors. This will print a message after 4 seconds, then terminate the program. <lang Mathematica>Print["Will exit in 4 seconds"]; Pause[4]; Quit[] ->Will exit in 4 seconds</lang>
Oz
Events can be implemented as mutable references to dataflow variables:
<lang oz>declare
fun {NewEvent} {NewCell _} end
proc {SignalEvent Event} @Event = unit end
proc {ResetEvent Event} Event := _ end
proc {WaitEvent Event} {Wait @Event} end
E = {NewEvent}
in
thread {System.showInfo "[2] Waiting for event..."} {WaitEvent E} {System.showInfo "[2] Received event."} end
{System.showInfo "[1] Waiting 1 second..."} {Delay 1000} {System.showInfo "[1] Signaling event."} {SignalEvent E}</lang>
However, this code is quite unidiomatic. If we need to wait for an event just once (like in this example), we can simply use a dataflow variable, i.e. an event that cannot be reset: <lang oz>declare
E
in
thread {System.showInfo "[2] Waiting for event..."} {Wait E} {System.showInfo "[2] Received event."} end
{System.showInfo "[1] Waiting 1 second..."} {Delay 1000} {System.showInfo "[1] Signaling event."} E = unit</lang>
If we want to synchronize two threads repeatedly and exchange data, it is natural to use ports and streams. Streams are just lists with an unbound tail. A port is basically a pointer to the tail of a list, i.e. it keeps track of where the next event can be written to: <lang oz>declare
MyPort
in
thread MyStream in {NewPort ?MyStream ?MyPort} {System.showInfo "[2] Waiting for event..."} for Event in MyStream do
{System.showInfo "[2] Received event."} {System.showInfo "[2] Waiting for event again..."}
end end
for do {System.showInfo "[1] Waiting 1 second..."} {Delay 1000} {System.showInfo "[1] Signaling event."} {Port.send MyPort unit} end</lang>
It is important to limit the scope of a stream as much as possible to ensure that the already read part of the stream is garbage-collected.
Perl
This is an example of using the AnyEvent module. The result is this: it prints "Hello world!" after one second, then after another second prints "Hi!" four times every quarter of a second and then immediately prints "Bye!" and quits: <lang Perl>use AnyEvent;
- a new condition with a callback:
my $quit = AnyEvent->condvar(
cb => sub { warn "Bye!\n"; }
);
- a new timer, starts after 2s and repeats every 0.25s:
my $counter = 1; my $hi = AnyEvent->timer(
after => 2, interval => 0.25, cb => sub { warn "Hi!\n"; # flag the condition as ready after 4 times: $quit->send if ++$counter > 4; }
);
- another timer, runs the callback once after 1s:
my $hello = AnyEvent->timer(
after => 1, cb => sub { warn "Hello world!\n"; }
);
- wait for the $quit condition to be ready:
$quit->recv();</lang>
This is the same using AnyEvent simplified API: <lang Perl>use AnyEvent;
my $quit = AE::cv sub { warn "Bye!\n" };
my $counter = 1; my $hi = AE::timer 2, 0.25, sub {
warn "Hi!\n"; $quit->send if ++$counter > 4;
};
my $hello = AE::timer 1, 0, sub {
warn "Hello world!\n";
};
$quit->recv;</lang>
PicoLisp
PicoLisp supports events from timers (via 'task' and 'alarm'), file descriptors (also 'task') and various 'signals'. This will print a message after one second, then terminate the program after another four seconds: <lang PicoLisp>(alarm 1
(prinl "Exit in 4 seconds") (alarm 4 (bye)) )</lang>
PureBasic
<lang Purebasic>OpenWindow (0, 10, 10, 150, 40, "Event Demo") ButtonGadget (1, 10, 10, 35, 20, "Quit")
Repeat
Event = WaitWindowEvent() If Event = #PB_Event_Gadget And EventGadget() = 1 End EndIf
ForEver</lang>
Tcl
Tcl has been event-driven since 7.5, but only supported channel and timer events (plus variable traces, which can be used to create event-like entitites). With the addition of coroutines, it becomes much simpler to create general events:
<lang tcl># Simple task framework built from coroutines proc pause ms {
after $ms [info coroutine];yield
} proc task {name script} {
coroutine $name apply [list {} \ "set ::tasks(\[info coro]) 1;$script;unset ::tasks(\[info coro])"]
} proc waitForTasksToFinish {} {
global tasks while {[array size tasks]} {
vwait tasks
}
}
- Make an Ada-like event class
oo::class create Event {
variable waiting fired constructor {} {
set waiting {} set fired 0
} method wait {} {
while {!$fired} { lappend waiting [info coroutine] yield }
} method signal {} {
set wake $waiting set waiting {} set fired 1 foreach task $wake { $task }
} method reset {} {
set fired 0
}
}
- Execute the example
Event create X task A {
puts "waiting for event" X wait puts "received event"
} task B {
pause 1000 puts "signalling X" X signal
} waitForTasksToFinish</lang>
Output:
waiting for event signalling X received event
Of course, the classic way of writing this is much shorter, but intermingles the tasks: <lang tcl>after 1000 set X signalled puts "waiting for event" vwait X puts "received event"</lang>