async / await

kio·2022년 11월 17일
0

Swift

목록 보기
6/11

async / await 를 처음 들었을 때?

func fetchSomeThing() async throws -> String {
	// ~~~
	let someThing = try! await fetch()
	// ~~~
}

함수 내부에서 URLSession을 통해서든 어떻게든 비동기적으로 함수가 실행되는 부분이 있으면,

함수가 종료되는 시점과 closure가 실행되는 시간이 다르다.

그럼 당연히 그 결과 값으로 무언가를 할려고 하면

 one() {
	two($0) {
		three($0) {
			four($0) {
				// ~~~ 	
		}	
	}
}

one이 부르고 나온 Escaping closure의 argument로 또 다른걸 부르고 하는 형태가 반복되기 때문에

아 sync를 붙이고 await를 붙이면 그냥 그 비동기함수가 완전히 끝날때까지 기다렸다 return 값을 통해 나에게 준다 라고 생각했다.

막 틀린건 아니던데?

사실 막 틀린것은 아닌것 같다.

하지만 그 스레드가 멈추고 다른 스레드에서 기다린다는 것은 아니다.

async / await

첫번째 질문!

왜 await는 async한 함수, Task 안에서만 사용이 가능한가?

아니 이 await는 비동기적으로 수행되는 함수를 동기적으로 수행하게 하는 건데 왜 위와 같은 조건에서만 사용가능하지?

생각해보면 쉽다!

만약 비동기 함수를 기다리는 곳이 main thread면? , 아니면 그 비동기 함수가 끝나질 않아서 계속 기다리게 된다면?

위와 같은 상황에서는 당연히 await한 부분을 버려야 한다. 그렇기 때문에

기다린다는 사실은 사실 비동기함수에서는 어울리지 않는다!

사실 진짜로 기다리는 건 동기함수이지 비동기 함수가 아니다.

진짜 기다리는건 thumnail이다 정확히는 기다리는 것보다 나에 대한 통제권을 동기함수에게 넘기고, 그 동기함수가 끝나는 시점에 다시 실행시켜주고 통제권을 반납한다.

두번째 질문!

await는 비동기 함수가 끝나기 기다리는 것이면, 난 그 동안 block 되어 있는건가?

이건 맞았다.

하지만 block이 되어서 resource가 unlock 될 때까지 context switching을 반복하면 thread를 만들지 않는다.
아까 말햇든 통제권을 넘기는 과정이기 때문에, 시스템에 통제권을 넘기고 해당 쓰레드는 그냥 자유가 된다

자유가 된다? 이 말인 즉 await가 붙은 지점을 만나는 순간 이 쓰레드는 시스템에 의해 다뤄진다. 그래서 await 뒤에 있는 비동기 함수가 실행된다.

한번더 정리하면 비동기함수가 다른 thread에서 실행되는게 아니라 해당 thread에서 실행 된다.

왜 why?

기존 GCD는 잘못 됬을 때, thread explosion이 발생할 수 있다. 예를들어 fetch할게 100개 있는데

한 fetch 마다 serialQueue 에 업데이트 한다면, block이 되고 그러는 동안 또 fetch 한다. 이러는 동안 거의 계속 새로운 thread를 만드는데 이렇게 되면 thread의 갯수가 너무 많아진다.

이렇게 되면 언젠가 다시 block 되어 있는 thread를 사용해야하기 때문에 그 최신 정보를 담는 메모리에 over head가 발생하고, thread가 너무 많아져서 스케쥴링에도 문제가 생길 수 있다. (당연히도 엄청 많은 context switching 이 발생)

하지만 Swift Concurrency는 조금 다른 정책을 적용한다.

  • CPU Core 수에 맞는 Thread만 사용한다.
  • Continuation 를 사용한다.

1번, CPU Core 수에 맞는 Thread만 사용하게 되면 당연히 thread 수가 부족해지는 현상이 있을 수 있다.

하지만 이 같은 문제는 위에 설명 했듯이 시스템이 block 되어 있는 Thread를 적절히 사용하면서 해결한다.

2번, 이렇게도 많은 Thread에서 처리하던 work item들이 하나의 thread에 몰리기 때문에 switching 시 문제가 생길 수 있다. 그래서 우리는 메모리에 무리를 덜 가게 하도록 한 Continuation이라는 다시 시작해야 할 부분을 표시한 continuation을 사용한다. 이는 아래와 같이 작동한다

  • 작업의 재개여부를 추적하고
  • Continuation 객체를 switching 한다.
  • Full Thread Context Switching대신 function call로 대체한다.

한 함수가 potential suspend point 즉 await 라고 쓰여진 부분을 보는 순간 block된다기 보단 해당 thread 가 자유가 되어 시스템에 맞겨진다. 그리고 async 함수가 시스템에 의해 다시 실행된다고 해서 꼭 원래 돌아가 thread에서 돌아가는 건 아닐 수 있다.

최종 정리

await을 붙인다는 것의 정리

해당 하는 async 함수는 potential suspend point 이고 그렇기 때문에 나머지 부분을 Continuation 객체로 만들어 list로 heap에 저장하고 해당 thread의 제어권을 system에 넘긴다.
그러다가 해당 작업이 필요해지는 시점에 한 Thread를 잡아( Core 수 이하의 즉 있던 Thread 일수도 ) Continuation을 불러 마저 실행한다.

async를 붙인다는 것의 정리

나라는 함수가 potential suspend point를 가지고 있기 때문에, 언제든 thread 제어권을 넘겨야 할 수 도있다.

추가사항

이러한 상황속에서 어떻게 race condition을 해결하나?

놀랍게도 컴파일 에러가 발생한다
비 독립적(non-isolated) 구문이 변할 수 있는 프로퍼티에 접근하는 것을 금지한다는 메시지
가 발생한다.
쉽게 말하면 원래 있던 Thread에서 독립적인 것들만 접근할 수 있다는 것이다.

그럼 Thread를 넘나 들면서 Thread safe하게 resource를 접근할수 없나?

이는 추후에 다루겠습니다~

0개의 댓글