mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
688 lines
27 KiB
ReStructuredText
688 lines
27 KiB
ReStructuredText
:orphan:
|
|
|
|
.. @raise litre.TestsAreMissing
|
|
.. ConcurrencyModel:
|
|
|
|
Swift Thread Safety
|
|
===================
|
|
|
|
This document describes the Swift thread-safety layer. It includes the
|
|
motivation for allowing users to write thread-safe code and a concrete proposal
|
|
for changes in the language and the standard library. This is a proposal and
|
|
not a plan of record.
|
|
|
|
The low-level thread-safety layer allows third-party developers to build
|
|
different kinds of high-level safe concurrency solutions (such as Actors,
|
|
Coroutines, and Async-Await) in a library. This document describes three
|
|
different high-level concurrency solutions to demonstrate the completeness and
|
|
efficacy of the thread-safe layer. Designing an official high-level concurrency
|
|
model for Swift is outside the scope of this proposal.
|
|
|
|
Motivation and Requirements
|
|
---------------------------
|
|
|
|
Multi-core processors are ubiquitous and most modern programming languages, such
|
|
as Go, Rust, Java, C#, D, Erlang, and C++, have some kind of support for
|
|
concurrent, parallel or multi-threaded programming. Swift is a safe programming
|
|
language that protects the user from bugs such as integer overflow and memory
|
|
corruption by eliminating undefined behavior and by verifying some aspects of
|
|
the program's correctness at runtime. Multi-threaded programs in Swift should
|
|
not break the safety guarantees of the language.
|
|
|
|
Swift Memory Model
|
|
------------------
|
|
|
|
This section describes the guarantees that unsupervised Swift provides when
|
|
writing multi-threaded code and why the minimal guarantees of atomicity that
|
|
Java provide cannot be implemented in Swift.
|
|
|
|
Let's start by looking at reference counting operations. Swift objects include a
|
|
reference-count field that holds the number of references that point to that
|
|
object. This field is modified using atomic operations. Atomic operations
|
|
ensure that the reference count field is not corrupted by data races. However,
|
|
using atomic operations is not enough to ensure thread safety. Consider the
|
|
multi-threaded program below.
|
|
|
|
.. code-block:: swift
|
|
|
|
import Foundation
|
|
|
|
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
|
|
let queue = dispatch_get_global_queue(priority, 0)
|
|
|
|
class Bird {}
|
|
var single = Bird()
|
|
|
|
dispatch_async(queue) {
|
|
while true { single = Bird() }
|
|
}
|
|
while true { single = Bird() }
|
|
|
|
This program crashes very quickly when it tries to deallocate an already
|
|
deallocated class instance. To understand the bug try to imagine two threads
|
|
executing the SIL code below in lockstep. After they both load the same value
|
|
they both try to release the object. One thread succeeds and deallocates the
|
|
object while another thread attempts to read the memory of a deallocated
|
|
object::
|
|
|
|
%10 = global_addr @singleton : $*Bird
|
|
|
|
bb:
|
|
%49 = alloc_ref $Bird
|
|
%51 = load %10 : $*Bird
|
|
store %49 to %10 : $*Bird
|
|
strong_release %51 : $Bird
|
|
br bb
|
|
|
|
Next, we'll look into the problem of sliced values. Intuitively, it is easy to
|
|
see why sharing memory between two threads could lead to catastrophic bugs.
|
|
Consider the program below::
|
|
|
|
Thread #1: Thread #2:
|
|
A.first = "John" A.first = "Paul"
|
|
A.last = "Lennon" A.last = "McCartney"
|
|
|
|
If thread #1 goes to sleep after executing the first statement and resumes
|
|
execution after thread #2 runs, then the value of the struct would be "Paul
|
|
Lennon", which is not a valid value. The example above demonstrates how data
|
|
races can introduce an inconsistent state.
|
|
|
|
The Java memory model ensures that pointers and primitive types, such as ints
|
|
and floats, are never sliced, even when data races occur. It would be nice if
|
|
Swift had a similar guarantee. Intuitively we would want all struct or class
|
|
members of primitive types to be aligned to ensure atomic access to the field.
|
|
However, this is not possible in Swift. Consider the Swift code below::
|
|
|
|
enum Fruit {
|
|
case Apple(Int64),
|
|
case Grape(MyClass)
|
|
}
|
|
|
|
The size of the struct above is 65 bits, which means that even on 64bit
|
|
processors the tag of the enum can't be updated at the same time as the payload.
|
|
In some race conditions we could accidentally interpret the payload of the
|
|
struct as pointer using the value stored into the integer.
|
|
|
|
To summarize, Swift, just like C++, does not make any guarantees about
|
|
unsynchronized memory, and the semantic of programs with races is undefined. When
|
|
race conditions occur pointers and primitive data types could be sliced, enums
|
|
may contain the wrong tag, protocols may refer to invalid dispatch tables,
|
|
references may point to deallocated objects.
|
|
|
|
Achieving thread safety
|
|
-----------------------
|
|
|
|
This section describes a set of rules that ensure thread safety in programs that
|
|
embrace them despite the inherit lack of thread safety in general multi-threaded
|
|
Swift code.
|
|
|
|
Safe concurrency is commonly implemented by eliminating shared mutable memory.
|
|
Go, Erlang and Rust ensure some level of program safety by providing mechanisms
|
|
for eliminating shared mutable memory. Erlang provides the strongest model by
|
|
ensuring complete logical address space separation between threads. Rust
|
|
provides powerful abstraction and rely on the type system to ensure that objects
|
|
are owned by a single entity. Go provides channels that allow threads to
|
|
communicate instead of sharing memory (but allows user to pass pointers in
|
|
channels!). It is not necessary to disallow all sharing of mutable data between
|
|
threads and it is not necessary to enforce a hermetic separation between the
|
|
address spaces. It is very useful to be able to share large data structures
|
|
without copying them around. Mutable data can be shared between threads as long
|
|
as the access to the data is synchronized and some program properties are
|
|
verified by the compiler. In Swift thread safety is implemented by preventing
|
|
threads from sharing mutable memory.
|
|
|
|
|
|
Proposal
|
|
========
|
|
|
|
In Swift, new threads are created in a new memory enclave that is separate from
|
|
the parent thread. Values can be copied in and out of the new thread context,
|
|
but the child thread must never obtain a reference that points to the outside
|
|
world. Non-reentrant code needs to be explicitly marked as such. Swift enforces
|
|
these rules statically. The rest of this section describes how Swift ensures
|
|
safety and deals with global variables and unsafe code.
|
|
|
|
The three basic elements of thread safety
|
|
-----------------------------------------
|
|
|
|
The Swift language has three features that allow it to ensure thread safety
|
|
and enforce it at compile time:
|
|
|
|
1. Copyable Protocol
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
The **Copyable protocol** marks types of instances that can be copied from one
|
|
thread context to another.
|
|
|
|
Instances of some types, such as Int, can be copied safely between threads
|
|
because they do not contain references that allow threads to access memory that
|
|
they do not own. Some types, such as String and Array (with copyable elements)
|
|
can be copied between thread context because they have value semantics and the
|
|
internal reference is not exposed.
|
|
|
|
The compiler derives the conformance of POD types and trivial enums to the
|
|
Copyable protocol automatically. Library designers need to manually mark types
|
|
with value semantics as Copyable.
|
|
|
|
Value-semantic types are not the only category of types that can be copied.
|
|
Library designers can implement thread-safe or lockless data structures and
|
|
manually mark them as Copyable.
|
|
|
|
Notice that due to rdar://17144340 we still can't mark Arrays and Optionals as
|
|
copyable::
|
|
|
|
// Optionals are copyable if the payload type is copyable.
|
|
extension Optional : CopyableType where T : CopyableType {}
|
|
|
|
2. Reentrant code
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
We ensure thread-safety by requiring that code that's executed from a worker
|
|
thread to only access logical copies of data that belongs to other threads. One
|
|
way for user code to break away from the memory enclave is to access **global
|
|
variables**. The Swift compiler must verify that threaded code does not access
|
|
global variables or unsafe code that it can't verify. There are exceptions to
|
|
this rule and the compiler provides special annotations for code that performs
|
|
I/O or calls unsafe code.
|
|
|
|
**Reentrant** code is code that only accesses memory that is accessible from the
|
|
passed arguments. In other words, reentrant code does not access global
|
|
variables or shared resources.
|
|
|
|
The thread verifier needs to be able to analyze all of the code that could
|
|
potentially be executed by a work thread and ensure that it is reentrant.
|
|
Dynamically dispatched calls, file and module boundary limit the efficacy
|
|
of the thread-verifier. This means that the information of whether a function is
|
|
reentrant or not needs to be a part of the **function signature**.
|
|
|
|
The **unsafe** attribute is used to denote code that is allowed to access global
|
|
variables and unsafe code. Objective-C methods are automatically marked as
|
|
**'unsafe'** unless they are explicitly marked with the **safe** attribute. The
|
|
`safe` and `unsafe` attributes provide a migration path for large bodies of code
|
|
that do not explicitly mark the APIs as reentrant or non-reentrant.
|
|
|
|
In the example program below the method `fly` may access the global variable
|
|
because it is marked with the attribute `unsafe`. The compiler won't allow this
|
|
method to be executed from a worker-thread.
|
|
|
|
.. code-block:: swift
|
|
|
|
var glob : Int = 1
|
|
|
|
class Bird {
|
|
unsafe func fly() { glob = 1}
|
|
}
|
|
|
|
In the example program below the `issafe` wrapper is used to explicitly mark a
|
|
region as safe. The developer is pacifying the compiler and explicitly marking
|
|
the code as safe.
|
|
|
|
The function ``logger`` is still considered by the compiler as reentrant and can
|
|
be called by worker-threads.
|
|
|
|
.. code-block:: swift
|
|
|
|
func logger(x : Int) {
|
|
|
|
// I know what I'm doing!
|
|
issafe {
|
|
glob = x
|
|
}
|
|
}
|
|
|
|
|
|
Most protocols in the standard library, like `Incrementable` and `Equatable` are
|
|
annotated as safe by default.
|
|
|
|
3. Gateways annotation
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Gateway annotation is a special semantics annotation that marks functions that
|
|
create new threads. This allows the compiler to verify that all of the
|
|
arguments that are passed to the thread conform to the Copyable protocol and
|
|
that the code that is executed by the worker thread is reentrant.
|
|
|
|
The compiler also verifies a few requirements that are special to the thread
|
|
creation site, like making sure that the closure to be executed does not capture
|
|
local mutable variables.
|
|
|
|
Library developers who implement high-level concurrency libraries can use the
|
|
gateway annotation to mark the functions that launch new threads.
|
|
|
|
.. code-block:: swift
|
|
|
|
@_semantics("swift.concurrent.launch")
|
|
public func createTask<ArgsTy>(args : ArgsTy, callback : (ArgsTy) -> Void) {
|
|
...
|
|
}
|
|
|
|
|
|
Summary
|
|
-------
|
|
|
|
Together, the thread verifier, the Copyable protocol, and the gateway annotation
|
|
allow us to implement the thread-safety layer. The rest of this document demonstrates
|
|
how these features are used for the implementation of high-level
|
|
concurrency systems.
|
|
|
|
The implementations of the thread-safety layer, the thread verifier, and
|
|
programs that use the three concurrency libraries are available in the
|
|
``concurrency`` git branch.
|
|
|
|
Implementing safe Go-lang style concurrency
|
|
===========================================
|
|
|
|
In this section, we describe how the proposed thread-safety layer can be used for
|
|
implementing go-lang style concurrency. Go supports concurrency using
|
|
coroutines and channels. We are going to demonstrate how to
|
|
implement go-style concurrency using verified code, CopyableType protocol
|
|
and gateway annotations.
|
|
|
|
Let's start by implementing Streams, which are analogous to go channels. A
|
|
stream is simply a blocking queue with restrictions on the types that can be
|
|
passed. Streams are generic data structures where the queue element type is
|
|
``CopyableType`` (and conforms to the relevant protocol, discussed above).
|
|
Streams are the only legitimate channel of communication between threads.
|
|
|
|
Streams can be shared by multiple tasks. These tasks can read from and write into the stream
|
|
concurrently. Reads from streams that contain no data and writes into full streams
|
|
will be blocked, meaning that the operating system will put the calling thread to sleep and wait for
|
|
new data to arrive to wake the sleeping thread.
|
|
This property allows the Stream to be used as a synchronization mechanism.
|
|
|
|
The second half of the go concurrency feature is coroutines. In Swift lingo,
|
|
we'll call them Tasks. Tasks are functions that are executed by threads
|
|
asynchronously. Tasks could have their own stack (this is an implementation
|
|
detail that is not important at this point) and can run indefinitely. Tasks are
|
|
created using gateways (see above) that ensure thread safety.
|
|
|
|
Together tasks and streams create a thread-safe concurrency construct. Let's
|
|
delve into this claim. Tasks are created using gateways that ensure that all
|
|
arguments being passed into the closure that will be executed are
|
|
CopyableType. In other words, all of the arguments are either deep-copied or
|
|
implemented in a way that will forbid sharing of memory. The gateway also
|
|
ensures that the closure that will be executed by the task is verified, which
|
|
means that it will not access global variables or unsafe code, and it will not capture
|
|
any variable that is accessible by the code that is creating the task. This
|
|
ensures a hermetic separation between the newly created thread and the parent
|
|
thread. Tasks can communicate using streams that ensure that information that
|
|
passes between threads, just like the task's closure arguments, does not leak
|
|
references and keeps the hermetic separation between the tasks. Notice that
|
|
Streams themselves are CopyableTypes because they can be copied freely between
|
|
tasks without violating thread safety.
|
|
|
|
Stream and Tasks provide safety and allow users to develop server-like tasks
|
|
easily. Reading requests from a queue, processing the request and writing it into
|
|
another queue are easy, especially since the queues themselves provide the
|
|
synchronization mechanisms. Deadlocks manifest themselves as read requests from
|
|
an empty queue, which makes debugging and reasoning about these bugs trivial.
|
|
|
|
Usage Example
|
|
-------------
|
|
This is an example of a tiny concurrent program that uses Tasks and Streams.
|
|
|
|
.. code-block:: swift
|
|
|
|
let input = Stream<String>()
|
|
let output = Stream<String>()
|
|
|
|
func echoServer(inp : Stream<String>,
|
|
out : Stream<String>) {
|
|
while true { out.push(inp.pop()) }
|
|
}
|
|
|
|
createTask((input, output), callback: echoServer)
|
|
|
|
for val in ["hello","world"] {
|
|
input.push(val)
|
|
print(output.pop())
|
|
}
|
|
|
|
The program above creates a server task that accepts an input stream and an
|
|
output stream that allows it to communicate with the main thread. The compiler
|
|
verifies that the task does not access any disallowed memory locations (as
|
|
described below).
|
|
|
|
It is entirely possible to remove the manual declaration of the streams and the
|
|
argument types and define a single endpoint for communication with the new task.
|
|
In the example below the type declaration of the endpoint helps the type checker
|
|
to deduct the type of the stream arguments and allows the developer to omit the
|
|
declaration of the streams in the closure.
|
|
|
|
.. code-block:: swift
|
|
|
|
let comm : _Endpoint<String, Int> = createTask({var counter = 0
|
|
while true {
|
|
$0.pop()
|
|
$0.push(counter++)
|
|
}})
|
|
// CHECK: 0, 1, 2,
|
|
for ss in ["","",""] {
|
|
comm.push(ss)
|
|
print("\(comm.pop()), ", appendNewline: false)
|
|
}
|
|
|
|
Stream utilities
|
|
----------------
|
|
The Swift library can to implement a few utilities that will allow users and
|
|
library designers to build cool things:
|
|
|
|
* The ``Funnel`` class accepts multiple incoming streams and weaves them into a
|
|
single outgoing stream.
|
|
|
|
* The ``Fan-out`` class accepts a single incoming stream and duplicates the
|
|
messages into multiple outgoing streams.
|
|
|
|
* The ``waitForStream`` function accepts multiple Streams and returns only when
|
|
one or more of the streams are ready to be read.
|
|
|
|
It is entirely possible to implement MPI-like programs that broadcast messages
|
|
or send messages to a specific task. It is also very easy to implement barriers
|
|
for SPMD-like programs using fan-out stream.
|
|
|
|
|
|
Implementing Async - Await
|
|
==========================
|
|
|
|
Async-Await is one of the most popular and effective concurrency solutions. In
|
|
this section we describe how the proposed thread-safety layer can be used for
|
|
implementing Async-Await style concurrency.
|
|
|
|
Async calls are function calls that return a Future, which is a mechanism that
|
|
allows the caller of asynchronous procedures to wait for the results. The async
|
|
call execute the callback closure in a secure enclave to ensure thread safety.
|
|
|
|
Example
|
|
-------
|
|
Example of a concurrent program using Futures in Swift.
|
|
|
|
.. code-block:: swift
|
|
|
|
func merge_sort<T : Comparable>(array: ArraySlice<T>) -> [T] {
|
|
|
|
if array.count <= 16 { return Array(array).sort() }
|
|
|
|
let mid = array.count / 2
|
|
let left = array[0..<mid]
|
|
let right = array[mid..<array.count]
|
|
|
|
let lf = async(left, callback: merge_sort)
|
|
let lr = async(right, callback: merge_sort)
|
|
|
|
return merge(lf.await(), lr.await())
|
|
}
|
|
|
|
The program above uses async to execute two tasks that sorts the two halves of
|
|
the array in parallel. Notice that the arrays in the example above are not
|
|
copied when they are sent to and from the async task. Swift arrays are
|
|
copy-on-write value types and when an array is copied the underlying storage is
|
|
not copied with it. This feature of arrays allows swift to share arrays between
|
|
threads in a safe manner without copying data.
|
|
|
|
Here is another example of async calls using trailing closures and enums.
|
|
|
|
.. code-block:: swift
|
|
|
|
enum Shape {
|
|
case Circle, Oval, Square, Triangle
|
|
}
|
|
|
|
let res = async(Shape.Oval) {(c : Shape) -> (String) in
|
|
switch c {
|
|
case .Circle: return "Circle"
|
|
case .Oval: return "Oval"
|
|
case .Square: return "Square"
|
|
case .Triangle: return "Triangle"
|
|
}}
|
|
|
|
//CHECK: Shape: Oval
|
|
print("Shape: \( res.await() )")
|
|
|
|
Notice that the swift compiler infers that ``Shape`` and `String` can be sent
|
|
between the threads.
|
|
|
|
UI programming with Async
|
|
-------------------------
|
|
|
|
One of the goals of this proposal is to allow users to develop multi-threaded UI
|
|
applications that are safe.
|
|
|
|
At the moment Swift users that use GCD are advised to start a new block in a new
|
|
thread. Once the task finishes the recommendation is to schedule another block
|
|
that will be executed by the main event loop.
|
|
|
|
Notice that the Async call returns a Future, and the callee needs to block on
|
|
the result of the Future. In this section we describe the extension to the
|
|
Async call that allows it to execute code on the main event loop asynchronously.
|
|
|
|
One possible solution would be to add an async call that accepts two closures.
|
|
One that's executed asynchronously, and another one that will be executed
|
|
synchronously after the task is finished. F# provides a similar API (with
|
|
StartWithContinuations).
|
|
|
|
One possible implementation is one where the task creation call return an object
|
|
that allows the users to register callbacks of different kinds. The destructor
|
|
of the task object would execute the work callback for convenience. The two
|
|
useful callbacks are "on completion" that would execute code in the main UI
|
|
thread and "on error" that would be executed in case of an exception in the work
|
|
closure.
|
|
|
|
This is a small example from an app that counts the number of prime numbers
|
|
between one and million concurrently. The first closure is the worker closure
|
|
that does all the work in a separate thread (and is verified by the thread
|
|
safety checker), and the second closure is executed by the UI main loop and is
|
|
free to make unsafe calls capture locals and access globals.
|
|
|
|
.. code-block:: swift
|
|
|
|
@IBAction func onClick(sender: AnyObject) {
|
|
|
|
progress.startAnimating()
|
|
Label!.text = ""
|
|
|
|
asyncWith (1_000_000) {
|
|
(num: Int) -> Int in
|
|
var sum = 0
|
|
for i in 1..<num {
|
|
if isPrime(i) { sum += 1 }
|
|
}
|
|
return sum
|
|
}.setOnComplete {
|
|
(x : Int) in
|
|
self.Label!.text = "Found \(x) primes.\n"
|
|
self.progress.stopAnimating()
|
|
}
|
|
|
|
}
|
|
|
|
Unsafe Concurrency with unsafeAsync
|
|
-----------------------------------
|
|
|
|
In many cases iOS users would need to use unsafe code such as code written in
|
|
Objective-C, or code that has access to shared mutable state. In the previous
|
|
section we mentioned that it is possible to mark some functions with a special
|
|
annotation that will signal to the verifier to stop the verification. For
|
|
example, the ``print`` function call would have to be marked with such an
|
|
annotation if we want people to be able to use it from thread-safe code. This
|
|
feature is useful for library developers, but not for app developers.
|
|
|
|
Some people may wish to skip the safety checks that the compiler provides and
|
|
write unsafe asynchronous code. The ``unsafeAsync`` can allow users to run
|
|
asynchronous code using Futures and async calls but without the safety checks.
|
|
|
|
The `async` call is actually a wrapper around unsafeAsync, except that it
|
|
contains the annotation that tells the verifier to verify that the code is
|
|
thread-safe (explained in the previous section). For example:
|
|
|
|
.. code-block:: swift
|
|
|
|
@_semantics("swift.concurrent.async")
|
|
// This annotation tells the compiler to verify the closure and the passed arguments at the call site.
|
|
public func async<RetTy, ArgsTy>(args : ArgsTy, callback : (ArgsTy) -> RetTy) -> Future<RetTy> {
|
|
return unsafeAsync(args, callback: callback)
|
|
}
|
|
|
|
Example of shared data structures
|
|
---------------------------------
|
|
|
|
In the example below the class PrimesCache is explicitly marked by the user as a
|
|
CopyableType. The user implemented a thread-safe class that allows concurrent
|
|
access to the method ``isPrime``. To implement a critical section the user
|
|
inherit the class ``Sync`` that contains a lock and a method that implements a
|
|
critical section. The user also had to annotate the shared method as safe
|
|
because the verifier has no way of knowing if the call is safe. Notice that the
|
|
critical section itself is not enough to ensure thread safety because the
|
|
critical section could be accessing memory that is shared between threads that
|
|
are not synchronized on the same lock.
|
|
|
|
.. code-block:: swift
|
|
|
|
final class PrimesCache : Sync, CopyableType {
|
|
var cache : [Int : Bool] = [:]
|
|
|
|
@_semantics("swift.concurrent.safe")
|
|
func isPrime(num : Int) -> Bool {
|
|
return self.critical {
|
|
if let r = self.cache[num] { return r }
|
|
let b = calcIsPrime(num)
|
|
self.cache[num] = b
|
|
return b
|
|
}
|
|
}
|
|
}
|
|
|
|
func countPrimes(P : PrimesCache) -> Int {
|
|
var sum = 0
|
|
for i in 2..<10_000 { if P.isPrime(i) { sum += 1} }
|
|
return sum
|
|
}
|
|
|
|
let shared = PrimesCache()
|
|
let R1 = async(shared, callback: countPrimes)
|
|
let R2 = async(shared, callback: countPrimes)
|
|
|
|
// CHECK: [1229, 1229]
|
|
print([R1.await(), R2.await()])
|
|
|
|
|
|
Example of parallel matrix multiply using Async
|
|
-----------------------------------------------
|
|
|
|
This is a small example of the parallel matrix multiplication algorithm using
|
|
async and futures. The slices of the matrix are not copied when they are moved
|
|
between the threads because ContiguousArray has value semantics and the parallel
|
|
code runs significantly faster.
|
|
|
|
.. code-block:: swift
|
|
|
|
func ParallelMatMul(A : Matrix,_ B : Matrix) -> Matrix {
|
|
assert(A.size == B.size, "size mismatch!")
|
|
|
|
// Handle small matrices using the serial algorithm.
|
|
if A.size < 65 { return SerialMatMul(A, B) }
|
|
|
|
var product = Matrix(A.size)
|
|
// Extract 4 quarters from matrices A and B.
|
|
let half = A.size/2
|
|
let A11 = A.slice(half ,0 , 0)
|
|
let A12 = A.slice(half ,0 , half)
|
|
let A21 = A.slice(half ,half, 0)
|
|
let A22 = A.slice(half ,half, half)
|
|
let B11 = B.slice(half ,0 , 0)
|
|
let B12 = B.slice(half ,0 , half)
|
|
let B21 = B.slice(half ,half, 0)
|
|
let B22 = B.slice(half ,half, half)
|
|
|
|
// Multiply each of the sub blocks.
|
|
let C11_1 = async((A11, B11), callback: ParallelMatMul)
|
|
let C11_2 = async((A12, B21), callback: ParallelMatMul)
|
|
let C12_1 = async((A11, B12), callback: ParallelMatMul)
|
|
let C12_2 = async((A12, B22), callback: ParallelMatMul)
|
|
let C21_1 = async((A21, B11), callback: ParallelMatMul)
|
|
let C21_2 = async((A22, B21), callback: ParallelMatMul)
|
|
let C22_1 = async((A21, B12), callback: ParallelMatMul)
|
|
let C22_2 = async((A22, B22), callback: ParallelMatMul)
|
|
|
|
// Add the matching blocks.
|
|
let C11 = C11_1.await() + C11_2.await()
|
|
let C12 = C12_1.await() + C12_2.await()
|
|
let C21 = C21_1.await() + C21_2.await()
|
|
let C22 = C22_1.await() + C22_2.await()
|
|
|
|
// Save the matrix slices into the correct locations.
|
|
product.update(C11, 0 , 0)
|
|
product.update(C12, 0 , half)
|
|
product.update(C21, half, 0)
|
|
product.update(C22, half, half)
|
|
return product
|
|
}
|
|
|
|
|
|
Implementing Actors
|
|
===================
|
|
|
|
In this section we describe how the proposed thread-safety layer can be used for
|
|
implementing Actor-based concurrency.
|
|
|
|
Actors communicate using asynchronous messages that don't block. Systems that
|
|
use actors can scale to support millions of concurrent actors because actors are
|
|
not backed by a live thread or by a stack.
|
|
|
|
In Swift actors could be implemented using classes that inherit from the generic
|
|
``Actor`` class. The generic parameter determines the type of messages that the
|
|
actor can accept. The message type needs to be of ``CopyableType`` to ensure the
|
|
safety of the model. The actor class exposes two methods: ``send`` and
|
|
``accept``. Messages are sent to actors using the ``send`` method and they never
|
|
block the sender. Actors process the message using the ``accept`` method.
|
|
|
|
At this point it should be obvious to the reader of the document why
|
|
marking the ``accept`` method as thread safe and allowing the parameter type to
|
|
be ``CopyableType`` will ensure the safety of the system (this is discussed at
|
|
length in the previous sections).
|
|
|
|
The ``accept`` method is executed by a user-space scheduler and not by live
|
|
thread and this allows the system to scale to tens of thousands of active
|
|
actors.
|
|
|
|
The code below depicts the famous prime numbers sieve program using actors. The
|
|
sieve is made of a long chain of actors that pass messages to one another.
|
|
Finally, a collector actor saves all of the messages into an array.
|
|
|
|
.. code-block:: swift
|
|
|
|
// Simply collect incoming numbers.
|
|
class Collector : Actor<Int> {
|
|
|
|
var numbers = ContiguousArray<Int>()
|
|
|
|
override func accept(x : Int) { numbers.append(x) }
|
|
}
|
|
|
|
// Filter numbers that are divisible by an argument.
|
|
class Sieve : Actor<Int> {
|
|
var div : Int
|
|
var next : Actor<Int>
|
|
|
|
init(div d : Int, next n : Actor<Int>) {
|
|
div = d ; next = n
|
|
}
|
|
|
|
override func accept(x : Int) {
|
|
if x != div && x % div == 0 { return }
|
|
next.send(x)
|
|
}
|
|
}
|
|
|
|
var col = Collector()
|
|
var head : Actor<Int> = col
|
|
|
|
// Construct the Sieve
|
|
for i in 2..<limit { head = Sieve(div: i, next: head) }
|
|
|
|
// Send all of the integers
|
|
for i in 2..<(limit*limit) { head.send(i) }
|
|
|
|
// CHECK: [1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
|
|
print(col.numbers.sort())
|
|
|