I would separate 2 things:
- Whether or not actors may share the same executor (it may be not a single instance, but for example a limited executor pool).
- Whether or not each instance of actor will run in parallel with other instance of the same actor type, regardless of how their executor is implemented. This part can be proven with this simple test:
actor A {
let name: String
init(_ name: String) {
self.name = name
}
func doSomething() {
print("\(name) started \(Date())")
sleep(UInt32.random(in: 1...3))
print("\(name) finished \(Date())")
}
}
Now lets create an instance of it:
let a1 = A("a1")
And run it on multiple threads like this:
DispatchQueue.concurrentPerform(iterations: 20) { i in
Task.detached(priority: .userInitiated) {
a1.doSomething()
}
}
Predictably we will get a sequence of executions:
a1 started 2022-07-03 16:42:22 +0000 on thread: <NSThread: 0x6000018d8080>{number = 8, name = (null)}
a1 finished 2022-07-03 16:42:23 +0000
a1 started 2022-07-03 16:42:23 +0000 on thread: <NSThread: 0x6000018f1c40>{number = 10, name = (null)}
a1 finished 2022-07-03 16:42:25 +0000
...
Now lets try to have 3 instances of the same actor:
let a1 = A("a1")
let a2 = A("a2")
let a3 = A("a3")
and run them the same way:
DispatchQueue.concurrentPerform(iterations: 20) { i in
Task.detached(priority: .userInitiated) {
switch i%3 {
case 0:
await a1.doSomething()
case 1:
await a2.doSomething()
case 2:
await a3.doSomething()
default:
await a1.doSomething()
So now you will see:
a2 started 2022-07-03 16:43:40 +0000 on thread: <NSThread: 0x60000229db80>{number = 10, name = (null)}
a3 started 2022-07-03 16:43:40 +0000 on thread: <NSThread: 0x600002290e80>{number = 9, name = (null)}
a2 finished 2022-07-03 16:43:41 +0000
a1 started 2022-07-03 16:43:41 +0000 on thread: <NSThread: 0x60000229db80>{number = 10, name = (null)}
a3 finished 2022-07-03 16:43:43 +0000
a2 started 2022-07-03 16:43:43 +0000 on thread: <NSThread: 0x600002290e80>{number = 9, name = (null)}
a1 finished 2022-07-03 16:43:44 +0000
a3 started 2022-07-03 16:43:44 +0000 on thread: <NSThread: 0x60000229db80>{number = 10, name = (null)}
a2 finished 2022-07-03 16:43:44 +0000
a1 started 2022-07-03 16:43:44 +0000 on thread: <NSThread: 0x600002290e80>{number = 9, name = (null)}
a3 finished 2022-07-03 16:43:45 +0000
a2 started 2022-07-03 16:43:45 +0000 on thread: <NSThread: 0x6000022a25c0>{number = 11, name = (null)}
a1 finished 2022-07-03 16:43:45 +0000
...
So that proves that each instance of actor will run in parallel with other instance of the same actor type. Practically this is all you need to know.
Now to the first question: it's clear that since 2 or more actor instances can be executed concurrently on sequential executor, there has to be more than one executor. But it also doesn't proof that each actor instance has its own executor: there may be a limited pool of executors, from which actor instance get their executor instance... This is something very hard to prove - would be easier to inspect the source code. Even the docs can be read both ways, for example in UnownedSerialExecutor the docs say:
For example, keeping an actor alive must also keep the actor’s associated executor alive; if they are different objects, the executor must be referenced strongly by the actor.
(i.e. what do they mean by "associated"? the one created specifically for the actor, or the one assigned to this actor from the pool?)
So this is the question, which I think can only be answered by Apple folks. Would be interesting to know the answer though.