Actor concurrent/parallel execution

Hello guys, could you tell me guys. Do swift actors support parallel execution overall?
After some source code research and few articles read - I make assumption that all custom actor direct they calls to global executor, which manages global thread pool. Exception is MainActor which is whole standalone thing.
I know that blocking operations inside task is not allowed, it may broke dispatching of executor, but I don't know any other ideas how to test it.

So my example is:

actor SomeActor {
    let name: String
    
    init(_ name: String) { self.name = name }
    
    func run() {
        print("Start run: (name)")
        sleep(5)
        print("End run: (name)")
    }
}
let act1 = SomeActor("A")
let act2 = SomeActor("B")
Task.detached {
    await act1.run()
}
Task.detached {
    await act2.run()
}
Output is: 
Start run: A
End run: A
Start run: B
End run: B

Why do actors run serially, taking into account that they are invoked from detached task?
Do actors support parallelism, or they are concurrent only?
Will appreciate any ideas and assumptions)

How are you testing this? What OS/environment?

If I change your code as follows to make it compile and to await the two tasks before exiting, and then run it on the command line (swift actors.swift), the two tasks do run concurrently as you would expect.

+import Foundation
+
 actor SomeActor {
     let name: String
     
     init(_ name: String) { self.name = name }
     
     func run() {
-        print("Start run: (name)")
+        print("Start run: \(name)")
         sleep(5)
-        print("End run: (name)")
+        print("End run: \(name)")
     }
 }
 let act1 = SomeActor("A")
 let act2 = SomeActor("B")
-Task.detached {
+let t1 = Task.detached {
     await act1.run()
 }
-Task.detached {
+let t2 = Task.detached {
     await act2.run()
 }
+
+await t1.value
+await t2.value

Output:

$ swift actors.swift
Start run: A
Start run: B
End run: B
End run: A

Environment: macOS 12.4, Swift 5.7 (Xcode 14.0b2)

Tried your changes still serially, my config are:
macOS 12.4
Apple Swift version 5.6 (swiftlang-5.6.0.323.62 clang-1316.0.20.8)
Target: x86_64-apple-macosx12.0
Xcode Version 13.3 (13E113)

How are you determining this? The order in which they complete is not deterministic.

Output is showing the actual order, btw question is not about order. It is about concurrency mechanism actor & executors use

I don't get this. My code doesn't even compile for me in Xcode 13.3 (or 13.4.1), presumably because SE-0343 Concurrency in Top-level Code was only implemented in Swift 5.7.

$ sudo xcode-select --switch /Applications/Xcode-13.3.app/Contents/Developer/

$ swift --version
swift-driver version: 1.45.2 Apple Swift version 5.6 (swiftlang-5.6.0.323.62 clang-1316.0.20.8)
Target: arm64-apple-macosx12.0

$ swift actors.swift
actors.swift:23:7: error: 'async' property access in a function that does not support concurrency
await t1.value
      ^
actors.swift:24:7: error: 'async' property access in a function that does not support concurrency
await t2.value
      ^

I did it inside another Task

Task {
     await t1
     await t2
}

I think it doesn't make any sense. Probably there are changes between versions of swift that bring something new.
Even these proposal could change it's implementation:

But if you do just that, the tasks won't even run to completion because the program will exit before the tasks finish, won't they?

With just this single change, the output is this for me (edit: macOS 12.4, Xcode 13.3 (Swift 5.6)):

$ swift actors.swift
Start run: B
Start run: A
# program exits here
$ (new prompt)

You'd need to add something like RunLoop.current.run() to the main program (at the top level) to keep the program running. And then it does the right thing again (the two actors run concurrently).

Is it 5.6 swift?
Even though it exits before finish, second tasks start before first one is ended

I'm testing it inside test project, inside appDelegate call.
So it's okay

Are you testing this in the iOS simulator?

Yup, have just tried real device and got:
Start run: B
Start run: A
End run: B
End run: A

What can be the reason of difference between sim and device? My iOS version 15.5

Just the same code in simulator runs serially

See, this is why it's important that you tell us your exact testing environment. We've been talking past each other.

Apparently this info from @rokhinip is still true:

The simulator needs to run on older OS-es which don't have the new cooperative thread pool with kernel support. As such, we have to make locally optimal decisions in libdispatch on how many threads to bring up in libdispatch

And:

If you had kicked off tasks with different QoS-es, you'd see that we will bring up at least a thread per QoS class but we don't have intelligent load balancing

So the simulator only uses one thread per task priority. Since your sleep call blocks the thread, nothing else can run on the cooperative thread pool in the meantime.

Thanks man, btw I recently noticed that on simulator Task.detached runs on: com.apple.root.default-qos.cooperative (serial)
And on device it's running on:
com.apple.root.default-qos.cooperative (concurrent)

Really appreciate your help)

Actors are concurrent only, and only one task can be executed on actor at the same time. There is no parallelism primitives in Swift Concurrency, still in Swift 5.7.

You mean no explicit primitives, like DispatchQueue.concurrentPerform, right? Because TaskGroups and async let are usually parallel, and independent Tasks are parallel if they don't touch the same resources.

Yes, exactly. Tanks for clarification.

I want additionally to notice that they can be parallel, but it is not guaranteed. Example:
(the implementation of concurrentMap() can be found here)

Task {
  let elementsArray = Array(1...1000)
  let maped = await elementsArray.concurrentMap { number in
    print("concurrentMap start \(number) ")
    defer { print("concurrentMap end \(number) ") }
    return (1...number).map { String(describing: $0) }
  }
}

Most often I see at first sequential prints:
concurrentMap start 1
concurrentMap end 1
concurrentMap start 2
concurrentMap end 2
concurrentMap start 3
concurrentMap end 3
concurrentMap start 4
concurrentMap end 4
...

Later I see lots of task first created:
AsyncMap start 994
AsyncMap start 995
AsyncMap start 996
...

and then finished
AsyncMap end 994
AsyncMap end 995
AsyncMap end 996
....

The order of prints changes on every execution, which is expected.

After experimenting with actors I found that while the actor is doing something everything stays on the same thread even if using withTaskGroup (each task would stay on the same thread). I’m not sure if, when or why an actor might switch threads (possibly if using async await and the actor has been idle during an await?). I don’t think this is guaranteed by any kind of spec, it just appears to be so and it makes total sense.

On the other hand using classes allows withTaskGroup to use multiple threads and each await is likely to wake up on a different thread just like the docs say.

For the simple case of only using async/await for parallelism, all blocks of code inside an actor which run without encountering any awaits would be synchronous to that actor’s thread so it will never run in parallel with other code in the actor because that’s how threads work.

However, once an await is introduced in the call stack, even if it doesn’t happen experimentally all the time (like some of you tried), code would run “in parallel” to other code in the actor. I’m using quotes because it is not true parallelism (not on a different thread) but logic parallelism, in the sense that once the await is encoutered other awaiting code elsewhere in the actor will be allowed to continue if it is ready to do so which can lead to bugs in some scenarions, so queues are wanted (or sometimes you can get away with a spinlock around using a while(){Task.sleep}, and sometimes with a simple FILO array for scheduling stuff).

I’ve discovered that in both V8 JS and Swift running the CPU at 100% even on the iPad/iPhone (even if using a separate tool like a burn-in test) causes many parallelism bugs to reveal themselves, for both multithreading (async/await in classes which uses multiple threads) and, separately, async/await in actors (same thread, order of execution bugs)

When using classes, unless they’re part of the @MainActor, it is very easy to run into multithreading issues when using async/await when modifying the same variables that other code is accessing or modifying.

Note: there are no actors in NodeJS and awaits always wake up on the same thread, while threads shared memory is handled by the programmer explicitly between threads (worker_threads).