It will take about 5 minutes to finish reading this article.
1. Concept of Structured-Concurrency
Structured-Concurrency is a programming paradigm that aims to improve code clarity and efficiency by using structured parallel programming methods.
Suppose there is a function that does a lot of work on the CPU. We want to optimize this by spreading the work across the two cores; so now the function creates multiple new threads, does a portion of the work in each thread, and then lets the original thread wait for the new thread to finish. There is some relationship (dependency, priority, synchronization, etc.) between the work done by these threads, but the system does not know it. This requires developers to write high-quality code to ensure.
In other words, Structured-Concurrency can help us better leverage the synergy of multi-core processors and greatly improve the performance of our code, but it may not be that easy to implement it manually.
In fact, in the Objective-C era, we can also implement Structured-Concurrency through some code, such as the following code we use GCD to implement:
1 | #import <Foundation/Foundation.h> |
It is important to note that when using GCD in Objective-C, you need to carefully manage communication and synchronization between threads and tasks to avoid potential race conditions and data sharing issues.
GCD provides various scheduling queues, semaphores, and other tools to help you implement more complex concurrency logic. However, this approach is not as intuitive or type-safe as Swift’s Structured-Concurrency. If you need more advanced concurrency control and error handling, you may want to consider writing your application in Swift.
2. Structured-Concurrency in Swift
Structured concurrency is mainly implemented in Swift through async let and task group.
The following is an asynchronous common code for downloading images:
1 | func fetchImage(from url: URL) async throws -> UIImage { |
2.1 Implement Structured-Concurrency by ‘async let’
1 | func downloadImages() async { |
Result:
1 | fetchImage ----- begin |
Note: the above code in the assignment expression of the leftmost plus async let instead of await, called async let binding, it is because of this binding operation, so that the previous fetchImage1, fetchImage2, fetchImage3 are encapsulated in a subtask waiting to be completed, this is the This is the key point of Structured-Concurrency.
2.2 Implement Structured-Concurrency by ‘task group’
1 | func downloadImages() async { |
Result:
1 | fetchImage ----- begin |
2.3 The difference between the two
First, not only the number of subtasks created by async let is static, but the order in which the subtasks are completed is also fixed, so it cannot obtain the results in the order in which the subtasks are completed. This determines that it is lighter and more intuitive. So if it can meet the needs, developers should give priority to async let.
Second, task groups can dynamically create subtasks and have more flexible operations, but they also require unified Closure encapsulation;
3. Unstructured tasks
In Apple’s 0304-structured-concurrency article, there is a mention of the unstructured concept. Compared to structured tasks (which mainly refer to parent-child tasks), then unstructured tasks mainly refer to some scenarios that run independently without relationship (parent-child). Unstructured tasks are mainly created by Task.init and Task.detached. These two ways to create the task, you can get the handle of the task, can be canceled, which is contrary to the structured concurrency. Specific sample code please see Task which chapter introduction.
Reference
[1] https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md
[2] https://juejin.cn/post/7084640887250092062
[3] https://en.wikipedia.org/wiki/Structured_concurrency
[4] http://chuquan.me/2023/03/11/structured-concurrency