Multitasking is implemented in the Libretto standard library. The basic components of concurrent computing in Libretto are the actor model and software transactional memory based on the optimistic approach.
Any Libretto program is executed in a system environment consisting of several actors. These actors are launched, when the program starts. The method
As an example, let us consider a ‘sleepy sort’, a funny sequence ordering algorithm, which is defined as follows. For each integer
The function
As an example, let us consider the customer-account management in a bank. First, introduce the class of financial transactions (please, do not confuse them with STM transactions):
The methods
Actor Model
In Libretto the actor model is basically adopted from Erlang. It is based on the concept of an actor – an active object, which asynchronously interacts with other actors. Actors communicate via messages.Actor
is a underdetermined class including methods act
, which defines the behavior of the actor, andstart
, which launches the actor.
Any Libretto program is executed in a system environment consisting of several actors. These actors are launched, when the program starts. The method
process
returns the actor, in which the current code is executed.
As an example, let us consider a ‘sleepy sort’, a funny sequence ordering algorithm, which is defined as follows. For each integer
n
from the sequence to be sorted a separate actor is launched, which gets n
and then falls asleep for n
secs. When it wakes up, the actor sends n
back to the dispatcher. The dispatcher forms the sequence of the integers in the order it receives them.
class SleepyActor(fix n: Int, fix dispatcher: Actor) extends Actor { def act { wait(n * 1000) dispatcher ! n } } def Int* sleepSort { var ordered var count = length this ?[$ >= 0] ?(error("negative!")). SleepyActor($, process). start loop receive { case n if Int => ordered += n count = count - 1 if (count == 0) return ordered } } (3,1,2,4).sleepSort // 1 2 3 4An actor here is an instance of the class
SleepyActor
, which:- receives integer
n
, and the dispatcher actor (the functionsleepSort
); - falls asleep for
n
secs; - on awake, sends
n
back to the dispatcher actor.
The function
wait
suspends execution for the given number of millisecs. The call dispatcher!n
, sends n
back to the dispatcher. The behavior of an actor is determined by the method act
. The method start
in the dispatcher creates a new thread and starts the actor in it. Having launched all sleepy actors, the dispatcher starts a loop, in which handles incoming numbers and counts them. The loop stops as soon as the last integer arrives.
Transactions
The concurrency control mechanism in Libretto is based on software transactional memory (STM). A transaction is executed within an atomic blockatomic { transaction body }which is seen from outside as single and indivisible. If a problem occurs (e.g. some resource suffers from simultaneous writes), then the transaction is rolled back to its initial state (all fields and variables marked by
ref
restore their initial values). The transaction is relaunched as soon as one of the fields or variables marked by ref
has changed its value. The programmer can relaunch the transaction ‘manually’ by using the method retry
. While a transaction is being executed, the changed values of ref
variables and ref
fields are not accessible from outside. And only after the successful completion of the transaction, the changes are committed and become accessible globally.
As an example, let us consider the customer-account management in a bank. First, introduce the class of financial transactions (please, do not confuse them with STM transactions):
class TransType object Income extends TransType object Expenditure extends TransType fix class Transaction(type: TransType, amount: Int, date: Date)Now let us define the class
Account
with two methods deposit
and withdraw
. Each of these method definitions contains a block atomic {...}
, which ensures the correctness of account transactions. In particular, it is impossible to withdraw money if its amount is insufficient on the account. If an account operation can not be fulfilled, its execution is suspended until the operation conditions are met. Several financial transactions can be made concurrently (in concurrent actors).
class Account(fix owner: Int) { ref var amount: Int = 0 ref var transactions: Transaction* // transactions history def withdraw(wamount: Int) { atomic { if (amount < wamount) retry // money not enough amount = amount - wamount transactions += Transaction(Expenditure, wamount, date) } } def deposit(damount: Int) { atomic { amount = amount + damount transactions += Transaction(Income, damount, date) } } }The predefined function
retry
stops the current execution of its atomic block, restores the initial state of variables and fields, and suspends the transaction until one of ref
-variables or ref
-fields change its value. In particular, the execution of the method withdraw
is suspended until the moment when the account has enough money.
The methods
error
and jump
(see section Exception Handling) behave differently in atomic blocks. If an exception is thrown by error
, and it can not be caught within this block, then the transaction is rolled back. By leaping out of the atomic block with jump
we say that its execution is completed successfully and the results of the transaction should be committed.
No comments:
Post a Comment