Task์ TaskGroup์ ๋ฌด์์ผ๊น? ๊ทธ๋ฆฌ๊ณ Apple์ด ๋งํ๋ Structured Concurrency๋ ๋ฌด์์ผ๊น?
Calling Asynchronous Functions in Parallel
์์ ๊ธ์์ ๋ณด์๋ฏ์ด await
ํค์๋๋ฅผ ์ฌ์ฉํ๋ฉด, ๋ค์์ฝ๋๋ก ๋์ด๊ฐ๊ธฐ ์ ์ ํธ์ถ์๋ ํด๋น ์์
์ ๋ง์น๋ ๊ฒ์ ๊ธฐ๋ค๋ฆฐ๋ค.(suspend)
let firstPhoto = await downloadPhoto(named: photoNames[0])
let secondPhoto = await downloadPhoto(named: photoNames[1])
let thirdPhoto = await downloadPhoto(named: photoNames[2])
let photos = [firstPhoto, secondPhoto, thirdPhoto]
show(photos)
์์ ๊ฐ์ ์ฝ๋๋ฅผ ์์ฑํ๋ค๋ฉด, 3๊ฐ์ ์ฌ์ง์ด ๋ค์ด๋ก๋ ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๊ฒ ๋๋ค. ์ด๋ฌํ ์ ๊ทผ ๋ฐฉ๋ฒ์๋ ๋จ์ ์ด ์๋๋ฐ, ๋์์ ์ฌ๋ฌ ์ฌ์ง์ ๋ค์ด๋ก๋ ๋ฐ์ ์ ์์์๋ ๋ถ๊ตฌํ๊ณ , ํ๋ฒ์ ํ๋์ ์ฝ๋๋ฅผ ๋๊ธฐํ๋ค๋ ๊ฒ์ด๋ค. ์ฐ๋ฆฌ๊ฐ ํ๊ณ ์ถ์ ๊ฑด, 3์ฅ์ ์ฌ์ง์ ๋ฐ์์ค๋ ํ์๋ฅผ ๋ณ๋ ฌ์ ์ผ๋ก ์ํํ๋ ๊ฒ์ด๋ค.
async let firstPhoto = downloadPhoto(named: photoNames[0])
async let secondPhoto = downloadPhoto(named: photoNames[1])
async let thirdPhoto = downloadPhoto(named: photoNames[2])
let photos = await [firstPhoto, secondPhoto, thirdPhoto]
show(photos)
๋์์ ์ํํ๊ธฐ ์ํด์๋, downloadPhoto
ํจ์ ์์ await
๋ฅผ ๋ถ์ฌ, ํด๋น ํจ์์ ๋์์ ๊ธฐ๋ค๋ฆฌ๋๋ก ํ์ง ์๊ณ , ํด๋น ๊ฐ์ ๋ฐ์์ค ๋ณ์ ์์ async
๋ฅผ ๋ถ์ฌ ํด๊ฒฐํ ์ ์๋ค. async let
๊ตฌ๋ฌธ์ ์ฌ์ฉํ๋ฉด, ๊ฐ๊ฐ์ ํ์๋ฅผ ๋
๋ฆฝ์ ์ผ๋ก ๋ค๋ฅธ ์ค๋ ๋์์ ๋์ํ๋๋ก ํ ์ ์๋ค. ์ต์ข
์ ์ผ๋ก ๋ฐ๋ ๊ฒฐ๊ณผ๋ ๋ชจ๋ ๋ด๊ฒจ์ผ ํ๊ธฐ ๋๋ฌธ์, photos
๋ณ์๋ฅผ ๋ฐ์ ๋ ์์ await
๋ฅผ ๋ฐ์์ ์ฒ๋ฆฌํ๋ฉด ๋๋ค.
- ๋น๋๊ธฐ ํจ์์ ์คํ ๊ฒฐ๊ณผ๊ฐ ์ํ์ ์ฝ๋์ ์์กด์ ์ธ ๊ฒฝ์ฐ(๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ ์์ฐจ์ ์ผ๋ก ์งํํด์ผ ํ๋ ๊ฒฝ์ฐ)
await
๋ฅผ ์ถ๊ฐํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๋๊ธฐํ ์ ์๋ค. - ๋น๋๊ธฐ ํจ์๋ฅผ ๋ณ๋ ฌ์ ์ผ๋ก ๋์ํ๊ณ ์ถ์ ๊ฒฝ์ฐ
async let
์ ์ฌ์ฉํ์. ์ด๋ด ๊ฒฝ์ฐ Parallelํ๊ฒ ๋์์ํฌ ์ ์๋ค. await
,async let
๋ชจ๋ ๋์ํ๊ณ ์๋ ์ค๋ ๋๋ฅผ suspendํ๊ณ ๋ค๋ฅธ ์ฝ๋๋ฅผ ์ํํ๋ ๊ฒ์ ํ์ฉํ๋ค.
Tasks and Task Groups
Task
๋ ํ๋ก๊ทธ๋จ์ ํน์ ๋ถ๋ถ์ ๋น๋๊ธฐ์ ์ผ๋ก ๋์ํ๊ฒ ํ ์ ์๋ work์ ๋จ์๋ค. ๋ชจ๋ asynchronous ์ฝ๋๋ Task์ ๋ถ๋ถ์ผ๋ก ์๋ํ๋ค. ์์์ ๋ณด์๋ async let
์, ๋ด๋ถ์ ์ผ๋ก child๋ฅผ ๋ง๋ค์ด์ฃผ๋ ํ์์ ๊ฐ๋ค. DispatchQueue์์ DispatchGroup์ ๋ง๋ ๊ฒ์ฒ๋ผ Task๋ Group์ผ๋ก ๊ด๋ฆฌํ ์ ์๋ค. Task
๋ฅผ ์ฌ์ฉํ๋ฉด ํน์ ์ฝ๋์ ๋์์ capsuleํ ํ์ฌ ๋
๋ฆฝ์ ์ผ๋ก ๋์ํ๋, ๋ณ๋ ฌ์ฑ๊น์ง ํ์ฉํ ์ ์๋ค.
Task๋ ์๊ณ ์ง์๋ฅผ ๊ฐ์ง๋ค. Task Group์์ ์๋ ๊ฐ๊ฐ์ Task๋ ๊ฐ์ ๋ถ๋ชจ task๋ฅผ ๊ฐ์ง๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ๊ฐ๊ฐ์ task๋ ์์ task๋ฅผ ๊ฐ์ง๋ค. ์ด๋ ๊ฒ task๋ค์ ๊ต์ฅํ ๋ช ๋ฐฑํ ๊ด๊ณ๋ฅผ ๊ฐ์ง๋๋ฐ, ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ structured concurrency๋ผ ๋ถ๋ฅธ๋ค. structured concurrency์ ํต์ฌ ์์ด๋์ด๋, Task๋ ๋ถ๋ชจ Task์ scope๋ฅผ ๋ฒ์ด๋ ์ ์๋ค๋ ๊ฒ์ด๋ค. ์ด๋ Task Group์๋ ์ ์ฉ๋๋ค. (์ถ๊ฐ๋๋ Child Task๊ฐ ์์ scope๋ฅผ ๋ฒ์ด๋ ์ ์๋ค๋ ์๊ธฐ)
Task Group
func getFavoriteIds(for user: User) async -> [UUID] {
return await network.fetchUserFavorites(for: user)
}
func fetchFavorites(user: User) async -> [Movie] {
// fetch Ids for favorites from a remote source
let ids = await getFavoriteIds(for: user)
// load all favorites concurrently
return await withTaskGroup(of: Movie.self) { group in
var movies = [Movie]()
movies.reserveCapacity(ids.count)
// adding tasks to the group and fetching movies
for id in ids {
group.addTask {
return await self.getMovie(withId: id)
}
}
// grab movies as their tasks complete, and append them to the `movies` array
for await movie in group {
movies.append(movie)
}
return movies
}
}
withTaskGroup(of:returning:body:)๋ฅผ ์ฌ์ฉํ๋ฉด taskGroup์ ์ฌ์ฉํ ์ ์๋ค. ์ฒซ๋ฒ์งธ ์ธ์๋ ์ด TaskGroup์ ํตํด ๋ฐํํ๋ ๊ฒฐ๊ณผ ํ์
์ ์ ์ด์ค๋ค. ๋ด๋ถ์์๋ group์ addTask
๋ฉ์๋๋ฅผ ํตํด Task๋ฅผ ์ถ๊ฐํ์ฌ Concurrentํ๊ฒ ๋์ํ๋๋ก ํ๋ค. addTask
๋ฅผ ํตํด ์ถ๊ฐํ๋ฉด, ๊ทธ์ ๋์์ concurrentํ๊ฒ ์ํ๋๋ค.
์ฌ๊ธฐ์ ์ฃผ๋ชฉํ ๋งํ ๋ถ๋ถ์, addTask
์ weakํ๊ฒ self
๋ฅผ captureํ์ง ์์๋ค๋ ๊ฒ์ด๋ค. ๊ทธ ์ด์ ๋, ๋ชจ๋ task์ ๋์์ ๋ชจ๋ ๊ธฐ๋ค๋ฆฐ ์ดํ์ returnํ๊ธฐ ๋๋ฌธ์ self
์ ์กด์ฌ scope๊ฐ withTaskGroup
์ผ๋ก ์ ํ๋๊ธฐ ๋๋ฌธ์ด๋ค. ์ด ๋ถ๋ถ์ด ์ดํด๊ฐ์ง ์์ ์ ์๋๋ฐ, ์๋๋ฅผ ๊ณ์ํด์ ์ฝ์ด๋ณด์.
๊ทธ๋ฐ๋ฐ, ์ด์ํ ์ ์ด ์๋ค. concurrentํ๊ฒ ๋์ํ๋ ๊ฒฐ๊ณผ๋ค์ ๋ชจ๋ ์์งํ์ง๋ ์์๋๋ฐ ๊ทธ ๋ค์ ์ฝ๋๋ฅผ ๋ณด๋ฉด, group์ loop๋ฅผ ๋๊ณ ์๋ค. ์ด ๋ AsyncSequence์์ ๋ณธ for try await
๊ตฌ๋ฌธ์ ์ฌ์ฉํ๊ณ ์๋ค. ์ด ๊ตฌ๋ฌธ์ ์ฌ์ฉํ ์ ์์ผ๋ ค๋ฉด, group
์ด AsyncSequence์ด์ด์ผ ํ๋ค. group
์ TaskGroup
Type์ธ๋ฐ, ์ค์ ๋ก AsyncSequence์ธ์ง ํ์ธํด๋ณด์.
/// ==== TaskGroup: AsyncSequence ----------------------------------------------
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension TaskGroup : AsyncSequence {
...
}
์ค์ ๋ก ๋ด๋ถ ์ฝ๋๋ฅผ ์ดํด๋ณด๋ฉด, TaskGroup์ด AsyncSequence๋ฅผ ์ฑํํ๊ณ ์์์ ํ์ธํ ์ ์๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ์๋์ ์ ํ ์ฃผ์์ ์ฝ์ด๋ณด์.
A type that provides an iteration interface over the results of tasks added to the group.
The elements returned by this iterator appear in the order that the tasks *completed*, not in the order that those tasks were added to the task group.
๊ทธ๋ฃน์ ์ถ๊ฐ๋ ์์
๊ฒฐ๊ณผ์ ๋ํด ๋ฐ๋ณต ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํ๋ ์ ํ์
๋๋ค.
์ด ๋ฐ๋ณต์์ ์ํด ๋ฐํ๋ ์์๋ ํ์คํฌ ๊ทธ๋ฃน์ ์ถ๊ฐ๋ ์์๊ฐ ์๋๋ผ ํ์คํฌ *์๋ฃ๋จ* ์์๋ก ๋ํ๋ฉ๋๋ค.
This iterator terminates after all tasks have completed. After iterating over the results of each task, it's valid to make a new iterator for the task group, which you can use to iterate over the results of new tasks you add to the group.
์ด ๋ฐ๋ณต๊ธฐ๋ ๋ชจ๋ ์์
์ด ์๋ฃ๋ ํ ์ข
๋ฃ๋ฉ๋๋ค. ๊ฐ ํ์คํฌ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๋ณตํ ํ ํ์คํฌ ๊ทธ๋ฃน์ ๋ํด ์ ๋ฐ๋ณต๊ธฐ๋ฅผ ๋ง๋๋ ๊ฒ์ด ์ ํจํฉ๋๋ค. ์ด ๋ฐ๋ณต๊ธฐ๋ฅผ ์ฌ์ฉํ์ฌ ๊ทธ๋ฃน์ ์ถ๊ฐํ๋ ์ ํ์คํฌ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๋ณตํ ์ ์์ต๋๋ค.
์ฆ, group์ task๊ฐ ์ถ๊ฐ๋๋ฉด concurrentํ๊ฒ ๋์์ด ์ํ๋๋ค. ์ด ๋์์ ์ํ ๊ฒฐ๊ณผ๋ task๋ฅผ ์ถ๊ฐ๋ ์์๋๋ก ๋ฐํ๋์ง ์๋๋ค. AsyncSequence๋ concurrentํ๊ฒ ๋์ํ๋ ์ฝ๋์ ๋ํด **๊ฒฐ๊ณผ๊ฐ ๋ฐํ๋ ์์๋๋ก iterator๊ฐ ๋์ํ์ฌ ๋ค์ ์์๋ฅผ ๋๊ฒจ์ค๋ค. **
TaskGroup์ด AsyncSequence๋ฅผ ์ฑํํ๊ณ ์๊ธฐ ๋๋ฌธ์, addTask
๋ก ์ถ๊ฐํ ๋์์ ๋ํ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ ๋๋ง๋ค for loop์์ ์ฒ๋ฆฌํ๋ค๋ ์ฌ์ค์ ์์๋ค. ๊ทธ๋ ๋ค๋ฉด ๋ชจ๋ task๊ฐ ๋ชจ๋ ์ฒ๋ฆฌ๋์๋ค๋ ๋์๋ค๋ ๊ฒ์ ์ด๋ป๊ฒ ์๋๊ฐ? ์ฆ, group์ 10๊ฐ์ ์์๊ฐ return ๋์ด์ผ ํ๊ณ , ์ด ์์๊ฐ ๋ชจ๋ ๋ฐํ๋์ด for await
๋ด๋ถ ๋์์ ๋ชจ๋ ์ฒ๋ฆฌํ ํ์์ผ movies
๊ฐ return ๋์ด์ผ ํ๋๋ฐ ์ด๋ฅผ ์ด๋ป๊ฒ ์ ์ ์์๊น?
์ด๋ ์์์ ๋ณธ AsyncSequence์์ ๊ทธ ์ค๋ง๋ฆฌ๋ฅผ ์ฐพ์ ์ ์๋ค. AsyncSequence๋ ๋ด๋ถ์ ์ผ๋ก iterator๋ฅผ ๊ฐ๋๋ฐ, ์ฌ๊ธฐ์ ๋ชจ๋ next ์์๋ฅผ ๋ฐํํ๋ค๋ฉด nil
์ ๋ฆฌํดํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด nil
์ ๊ธฐ๋ฐ์ผ๋ก ํด๋น loop๊ฐ ์ข
๋ฃ๋์์์ ํ์ธํ ์ ์๋ค. ํด๋น ๋งํฌ์ How it works ์ ์ ๋ณด๋ฉด loop๋ฅผ ์ด๋ป๊ฒ compiler๊ฐ ์ฒ๋ฆฌํ๋์ง ์ ์ ์๋ค.
Example
์ฌ๊ธฐ๊น์ง ์ฝ์ผ๋ฉด ํผ๋์ค๋ฌ์ธ ์ ์๋ค. ๊ฐ๋จํ๊ฒ ์ ๋ฆฌํด๋ณด๊ฒ ๋ค.
- Task์ TaskGroup์ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ๊ฐ๋๋ค. ์ด๋ฅผ structed concurrency๋ผ ํ๋ค.
- structed concurrency์์ ํ์ task๋ ์์ task์ ๋์ ์ ์ด๋ฅผ ๋ฐ๋๋ค. ๋ํ ํด๋น ์ค์ฝํ์ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ ์๋ค.
- TaskGroup์ ๊ฒฝ์ฐ AsyncSequence๋ฅผ ์ฑํํ๊ณ ์๋ค.
addTask
๋ก ์ถ๊ฐ๋ ๋ ์๋ค์ ๋ฐํ ์์๋๋ก group stream์ผ๋ก ์ฃผ์ ๋๋ค. addTask
๋ก ์ถ๊ฐ๋Task
๋ค์ด ๋ชจ๋ ์ข ๋ฃ๋๋ฉด ๋ด๋ถ์ ์ผ๋ก Iterator๊ฐ nil์ ๋ฐํํ๊ณ , for loop์ ์ข ๋ฃ๋๋ค.- AsyncSeqeunce์์์ ๋ง์ฐฌ๊ฐ์ง๋ก
for (try) await in
๊ตฌ๋ฌธ์์break
,continue
๋ฑ์ ์ฌ์ฉ๊ฐ๋ฅํ๋ค.
๊ทธ๋ ๋ค๋ฉด ์ค์ ๋ก ๊ทธ๋ฌํ์ง ์ฝ๋๋ก ์ดํด๋ณด์.
struct Data {
let id: Int
}
Task {
let results = await withTaskGroup(of: Data.self) { group -> [Data] in
let list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var datum = [Data]()
for number in list {
group.addTask {
let result = await self.doSomething(with: number)
print("Task Completed")
return Data(id: result)
}
}
print("For loop Completed")
for await data in group {
datum.append(data)
}
return datum
}
print("After task group called")
print("results: \(results)")
}
func doSomething(with number: Int) async -> Int {
let randomTime = Int.random(in: 1...3)
sleep(UInt32(randomTime))
print("number \(number) calculated")
return number
}
๊ฒฐ๊ณผ๋ ๋นํน์ค๋ฌ์ ๋ค.
For loop Completed
number 1 calculated
Task Completed
number 2 calculated
Task Completed
number 3 calculated
Task Completed
number 4 calculated
Task Completed
number 5 calculated
Task Completed
number 6 calculated
Task Completed
number 7 calculated
Task Completed
number 8 calculated
Task Completed
number 9 calculated
Task Completed
number 10 calculated
Task Completed
After task group called
results: [Concurrency.Data(id: 1), Concurrency.Data(id: 2), Concurrency.Data(id: 3), Concurrency.Data(id: 4), Concurrency.Data(id: 5), Concurrency.Data(id: 6), Concurrency.Data(id: 7), Concurrency.Data(id: 8), Concurrency.Data(id: 9), Concurrency.Data(id: 10)]
task๊ฐ ์ถ๊ฐ๋ ๊ฒฝ์ฐ, ํด๋น ์์ ์ค๋ ๋์ sleep์ ๊ฑธ์๋ค. ์์์ task์ ๋ฐํ ์์๋ก ๊ฒฐ๊ณผ๊ฐ ๋ค์ด๊ฐ๊ณ , ์ด๋ฅผ ์ฒ๋ฆฌํ๋ค๊ณ ํ๊ธฐ ๋๋ฌธ์, ๋ณ์น์ ์ผ๋ก ์ซ์๊ฐ ๋์ฌ ๊ฒ์ ์์ํ์ผ๋ ๊ฒฐ๊ณผ๋ ์์ฐจ์ ์ผ๋ก ๋์๋ค. ์ด๋ Sync OperationQueue์ ๊ฐ์ ๊ตฌ์กฐ์ task๋ค์ด ๋ค์ด๊ฐ ๊ฒฐ๊ณผ์ ๊ฐ๋ค. ์ด์ ๊ด๋ จ ๋ฌธ์ ๋ฅผ ์ฐพ์๋ณด์๋ค.
TaskGroup != Parallelism, == Concurrency
Is there an equivalent of DispatchQueue.concurrentPerform() with the new async/await?์์ ์ค๋ง๋ฆฌ๋ฅผ ์ฐพ์ ์ ์์๋ค.
There are no parallelism APIs with Swift concurrency that you can use that have the same behaviour of concurrentPerform.
One noteworthy distinction is that concurrentPerform in dispatch is not an asynchronous operation
- the caller thread participates in the operation and will block until all the operations in the concurrentPerform are completed.
concurrentPerform๊ณผ ๋์ผํ ๋์์ ํ๋ Swift ๋์์ฑ์ ๊ฐ์ง ๋ณ๋ ฌ API๋ ์์ต๋๋ค. ํ ๊ฐ์ง ์ฃผ๋ชฉํ ๋งํ ์ฐจ์ด์ ์ concurrentPerform in dispatch๋ ๋น๋๊ธฐ ์์
์ด ์๋๋ผ๋ ์ ์
๋๋ค. ์ฆ, ํธ์ถ์ ์ค๋ ๋๊ฐ ์์
์ ์ฐธ์ฌํ๊ณ concurrentPerform์ ๋ชจ๋ ์์
์ด ์๋ฃ๋ ๋๊น์ง ์ฐจ๋จ๋ฉ๋๋ค.
A TaskGroup might feel like a tempting solution but it provides structured concurrency not parallelism.
The dispatch equivalent for a TaskGroup would be to queue.async a bunch of work items to a concurrent queue
and group the work items together with a DispatchGroup.
It does not semantically provide you with the notion that the DispatchGroup is for a parallel compute workload,
which is what concurrentPerform does.
์ฆ, Task Group์ Parallellism ์ผ๋ก ๋์ํ์ง ์๊ณ Concurrency๋ผ๋ ๊ฒ์ด๋ค. ๋ด๋ถ์ ์ผ๋ก๋ DispatchQueue.async
์์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์๊ณผ ๋น์ทํ๊ฒ ์ฒ๋ฆฌ๋๋ค๋ ๊ฒ์ผ๋ก ํ์ธ๋๋ค.
์ฌ๊ธฐ์ concurrentPerform
์ ๋ช
์์ ์ผ๋ก Parallelism์ ์ฒ๋ฆฌํ๋ ํจ์๋ค. ์ด๋ฌํ API๋ฅผ ๋์ด์ ์ ๊ณตํ์ง ์์ผ๋ฉฐ, ์ผ๋ฐ์ ์ผ๋ก ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ API๋ ๋ชจ๋ Concurrency๋ผ๋ ์ ์ ๋ช
ํํ ํ๊ณ ์๋ค.
๋ฌธ์ ํด๊ฒฐ
๋ฌธ์ ๋ sleep
function์ ์์๋ค.
struct Data {
let id: Int
}
Task {
let results = await withTaskGroup(of: Data.self) { group -> [Data] in
let list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var datum = [Data]()
for number in list {
group.addTask {
let result = await self.doSomething(with: number)
print("Task Completed")
return Data(id: result)
}
}
print("For loop Completed")
for await data in group {
datum.append(data)
}
return datum
}
print("After task group called")
print("results: \(results)")
}
func doSomething(with number: Int) async -> Int {
let randomTime = Int.random(in: 1...3)
try? await Task.sleep(nanoseconds: UInt64(randomTime * 1_000_000_000)) โ
print("number \(number) calculated")
return number
}
For loop Completed
number 3 calculated
Task Completed
number 7 calculated
Task Completed
number 9 calculated
Task Completed
number 10 calculated
Task Completed
number 5 calculated
Task Completed
number 2 calculated
Task Completed
number 1 calculated
Task Completed
number 4 calculated
Task Completed
number 6 calculated
Task Completed
number 8 calculated
Task Completed
After task group called
results: [Concurrency.Data(id: 3), Concurrency.Data(id: 7), Concurrency.Data(id: 9), Concurrency.Data(id: 10), Concurrency.Data(id: 5), Concurrency.Data(id: 2), Concurrency.Data(id: 1), Concurrency.Data(id: 4), Concurrency.Data(id: 6), Concurrency.Data(id: 8)]
๊ทธ๋ ๋ค๋ฉด Thread.sleep()
๊ณผ Task.sleep()
์ ๋ฌด์์ด ๋ค๋ฅธ๊ฐ?
Thread.sleep vs. Task.sleep
๊ฐ์ฅ ํฐ ์ฐจ์ด๋, Thread.sleep()
๋ Thread๋ฅผ Blockํ๊ณ , Task.sleep()
๋ Thread๊ฐ ์๋ Task๋ฅผ Suspendํ๋ค๋ ์ ์ด๋ค. Suspendํ๋ ๊ฒฝ์ฐ ๋ค๋ฅธ ์์
์ ๊ฒฝ์ฐ ํด๋น Thread์์ ๊ณ์ ์งํํ ์ ์๋ค.
์ฐจ์ด์ ํ์ธํ๊ธฐ
let start = Date.timeIntervalSinceReferenceDate
DispatchQueue.concurrentPerform(iterations: 100) { _ in
Thread.sleep(forTimeInterval: 1)
}
let end = Date.timeIntervalSinceReferenceDate
print(String(format: "Duration: %.2fs", end-start))
// Duration: 9.00s
concurrentPerform
๋ฉ์๋๋ Apple์์ ์ ๊ณตํ๋ Parallelism API์ด๋ค. 100๊ฐ์ ๋ฐ๋ณต ๋์์ ์ํํ๊ณ , ๊ฐ๊ฐ์ thread์์ 1์ด๊ฐ sleep ํด๋ณธ๋ค๊ณ ํด๋ณด์. ์, ๊ทธ๋ฆฌ๊ณ ์ด concurrentPerform
๋ฉ์๋๋ ์์คํ
์์ ์ ๊ณตํ ์ ์๋ maximum core์์ ๋ง์ถฐ์ thread๋ฅผ ์์ฑํ๋ค. (์ถ์ฒ) ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ํด๋น ๋ฉ์๋๋ฅผ ์คํํ์ ๊ฒฝ์ฐ ๋ ๋ง์ ์ค๋ ๋๋ฅผ ๋ง๋ค์ด ์คํํ์ง ์๋๋ค.
์์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด ์ฝ 16 * 9 = 11.1, 11๊ฐ ์ ๋์ ์ค๋ ๋๊ฐ ์์ฑ๋์ด ์์ ์ ์ํํ๋ค๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
let start = Date.timeIntervalSinceReferenceDate
await withThrowingTaskGroup(of: Void.self, body: {
for _ in 0 ..< 100 {
$0.addTask {
try await Task.sleep(nanoseconds: 1_000_000_000)
}
}
})
let end = Date.timeIntervalSinceReferenceDate
print(String(format: "Duration: %.2fs", end-start))
// Duration: 1.05s
์ด๋ฒ์๋ Task์ sleep์ ๊ฑธ์์ ๊ฒฝ์ฐ๋ค. Task์ ๋์๋ง suspendํ๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ๋์์ ์ํํ ์ ์๊ฒ ๋๋ค.
Task๋ค์ Concurrent Queue๋ก ๋ค์ด๊ฐ๋๊ฐ?
๋ค์ ๊ฐ๋ค๋ฌ๊ณ ๋ฌด์์ด ๊ถ๊ธํ์ง ์ ๋ฆฌํด๋ณด์๋ค.
- Task๋ Parallelํ๊ฒ ๋์ํ์ง ์๋๊ฐ?
- ๊ฒฐ๊ตญ Task์ญ์ ์ด์ ์ GCD, OperationQueue์ ๋์์ wrapping ํ๋ ์น๊ตฌ๋ผ ์๊ฐํ๋ค.
- ๊ทธ๋ ๋ค๋ฉด ์ด Task๊ฐ ์ด๋ Thread, Queue์ ๋ค์ด๊ฐ๋์ง ๋๋ฒ๊น ์ ํด๋ณด์.
๊ทธ๋์ ์์ ์ฝ๋์ Break Point๋ฅผ ๊ฑธ์ด ์ด๋ป๊ฒ ๋์ํ๋์ง ์ดํด๋ณด์๋ค. ์ด ๋ ๋ณํํ๋ Thread๋ฅผ ๊ฐ๋ตํ๊ฒ ๋ํ๋ด๋ณด์๋ค.
Task {
print("Thread: \(Thread.current.debugDescription)") โ
let results = await withTaskGroup(of: Data.self) { group -> [Data] in
let list = (1...10).map { $0 }
var datum = [Data]()
for number in list {
group.addTask {
print("In AddTask Thread: \(Thread.current.debugDescription)") โ
let result = await self.doSomething(with: number)
print("After doSomething Thread: \(Thread.current.debugDescription)") โ
โ
โ
print("Task Completed")
return Data(id: result)
}
}
print("For loop Completed")
for await data in group {
datum.append(data)
}
return datum
}
print("After task group called")
print("results: \(results)")
}
private func doSomething(with number: Int) async -> Int {
print("In doSomething Thread: \(Thread.current.debugDescription)") โ
let randomTime = Int.random(in: 1...2)
try? await Task.sleep(nanoseconds: UInt64(randomTime * 1_000_000_000))
print("number \(number) calculated")
return number
}
๋จผ์ withTaskGroup
์ ์คํํ๋ ์๊ธฐ์๋ main thread ์๋ค. ๊ทธ๋ฆฌ๊ณ child task์ ๊ฒฝ์ฐ sub thread์์ ์ฒ๋ฆฌ๋๊ณ ์์๋ค. ํ์ง๋ง ์ด task ๋ด๋ถ์์ ๋ค๋ฅธ async ํจ์์ ๊ฒฐ๊ณผ๋ฅผ ๋๊ธฐํ๋ ๋ถ๋ถ์์ ๋์ํ๋ thread๊ฐ ๋ฌ๋ผ์ก๋ค. ๋ด๋ถ์ ์ผ๋ก async ํ๋ก์ธ์ค ์ญ์ ๋ค๋ฅธ task๋ฅผ ๋ง๋ค์ด ๋ค๋ฅธ thread๋ก ๋๊ฒจ์ฃผ๋ ํ์์ด๊ธฐ ๋๋ฌธ์ผ๋ก ์๊ฐ๋๋ค.
์ด ๋, main thread๊ฐ ํ๊ฐํ๊ธฐ ๋๋ฌธ์ ์ด ๊ณณ์ผ๋ก ๋์์ ๋๊ฒจ ์ฒ๋ฆฌํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ์ ๊ธฐํ ๊ฒ์ ์ด ๋ค์์ด์๋๋ฐ, ์ด๋ ๊ฒ await๋ก ๋์์ด ์๋ฃ๋์ด ๊ฒฐ๊ณผ๊ฐ ๋ฐํ๋์์์๋ ๋ถ๊ตฌํ๊ณ doSomething
์ ์ฒ๋ฆฌํ ํ ๋์์์ ๋์ Thread๋ sub thread๋ก ๋์์ค์ง ์์๋ค. (โ
โ
โ
ํ์) ์ด ๋ถ๋ถ์ ๋ด๋ถ์ ์ผ๋ก ์ด๋ป๊ฒ ๋์ํ๋์ง ํ์
ํ๊ธฐ ์ด๋ ค์ ๋ค.
Concurrency๋ ์ด๋ป๊ฒ ๋์ํ๋๊ฐ
์์ ์์ ์ ํ๋ฉด์, ์๊ฒ๋ ์ฌ์ค์ TaskGroup์ ๊ฒฝ์ฐ Serial Queue์์ ๋์ํ๋ค๋ ์ฌ์ค์ด๋ค. Concurrent Queue๊ฐ ์๋๋ค. ์ฆ Parallel์ด ๋์ํ์ง ์๋๋ค.
์ฆ, ์์ ๊ฐ์ด ๋์ํ๋ค.
ํด๋น ๊ทธ๋ฆผ์ WWDC 2017์ Modernizing Grand Central Dispatch Usage ์ ์๋ ๊ทธ๋ฆผ์ด๋ผ๊ณ ํ๋ค. ์ฐพ์๋ณด๋ ํด๋น ์์์ด ์ญ์ ๋ ๊ฒ์ธ์ง ํ์ธํ ์ ์์๋ค. GCD ์ ๋๋ก ์ฐ๊ธฐ ๊ธ์ ์ฐธ๊ณ ํ๋ฉด ๋ ์์ธํ ๋ด์ฉ์ ํ์ธํ ์ ์๋ค.
๋ง๋ฌด๋ฆฌ
ํ๋ํ์ง๋ง, ์์์ ์ ๋ฆฌํ ๋ด์ฉ์ ๋ฒ์ด๋์ง๋ ์์๋ค.
- Task์ TaskGroup์ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ๊ฐ๋๋ค. ์ด๋ฅผ structed concurrency๋ผ ํ๋ค.
- structed concurrency์์ ํ์ task๋ ์์ task์ ๋์ ์ ์ด๋ฅผ ๋ฐ๋๋ค. ๋ํ ํด๋น ์ค์ฝํ์ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ์ ์๋ค.
- TaskGroup์ ๊ฒฝ์ฐ AsyncSequence๋ฅผ ์ฑํํ๊ณ ์๋ค.
addTask
๋ก ์ถ๊ฐ๋ ๋ ์๋ค์ ๋ฐํ ์์๋๋ก group stream์ผ๋ก ์ฃผ์ ๋๋ค. addTask
๋ก ์ถ๊ฐ๋Task
๋ค์ด ๋ชจ๋ ์ข ๋ฃ๋๋ฉด ๋ด๋ถ์ ์ผ๋ก Iterator๊ฐ nil์ ๋ฐํํ๊ณ , for loop์ ์ข ๋ฃ๋๋ค.- AsyncSeqeunce์์์ ๋ง์ฐฌ๊ฐ์ง๋ก
for (try) await in
๊ตฌ๋ฌธ์์break
,continue
๋ฑ์ ์ฌ์ฉ๊ฐ๋ฅํ๋ค. - Task๋ Concurrency๋ฅผ ๋ง์กฑํ๋ค. Parallelism์ ๋ง์กฑํ์ง ์๋๋ค. ์ฆ, ํน์ ์ฐ์ฐ ์ฃผ์ฒด(์ฝ์ด)๋ฅผ ๋น ๋ฅด๊ฒ ๋ฒ๊ฐ์๊ฐ๋ฉด์ ์ฒ๋ฆฌํ๋ค.
concurrentPerform
๊ณผ ๊ฐ์ API๋ ์๋ค. (๋ณ๋ ฌ ์ฐ์ฐ)- ๋ณ๋ ฌ ์ฐ์ฐ์ ํ๋ ค๋ฉด
async let
์ ์ฌ์ฉํ์ฌ ๋ ๋ฆฝ์ ์ธ Task๋ฅผ ๋์ํ๋ ๊ฒ์ผ๋ก์ ๊ฐ๋ฅ์ผ ํ ์ ์๋ค. Thread.sleep()
์ Blocking,Task.sleep()
์ Suspend์ด๋ค.
Apple์ด ๋งํ๋ Structued Concurrency๋ Task๊ฐ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ์ด๋ฃจ๊ณ , ํ์ Task๋ ์์ Task๋ฅผ ๋ฒ์ด๋ ์ ์๋ค๋ ๊ฒ์ด ํต์ฌ ์์ด๋์ด์ด๋ค. TaskGroup
์ addTask
๋ฅผ ํ๊ฒ ๋๋ฉด ํน์ ์ค๋ ๋์ serial queue์ ๋ค์ด๊ฐ ๋ค concurrentํ๊ฒ ๋์ํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๋ด๋๋๋ค. group์ asyncSequence๋ฅผ ์ฑํํ๊ณ ์์ด ๋ชจ๋ sub task์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ ๋ค์ for await in
loop๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ค.
Reference
- Explore structured concurrency in Swift
- Protect mutable state with Swift actors
- Concurrency
- TaskGroup
- Running tasks in parallel with Swift Concurrencyโs task groups
- withTaskGroup(of:returning:body:)
- Is there an equivalent of DispatchQueue.concurrentPerform() with the new async/await?
- GCD ์ ๋๋ก ์ฐ๊ธฐ
- The difference between Thread.sleep() and Task.sleep()
- concurrentPerform(iterations:execute:)