iOS App GCD 사용 으로 인 한 끊 김 현상 및 해결 방법

10369 단어 iosgcd멈추다
최근 iOS 앱 에 존재 하 는 각종 카드 현상 과 해결 방법 을 조사 연구 했다.
iOS 앱 에 카드 가 걸 릴 확률 은 대부분의 사람들의 상상 을 뛰 어 넘 을 수 있 으 며,특히 대기업 플래그 십 앱 에 대해 서 는 그렇다.한편,업무 기능 이 계속 누적 되 고 각 제품 팀 간 의 조율 이 부족 하기 때문에 모두 기능 을 증가 하 느 라 바 쁘 고 시스템 자원 에 병목 이 생 겼 다.또 다른 이 유 는 기 존 기기 의 교체 가 너무 느 리 고 iOS 기기 의 내구성 이 뛰 어 나 현재 도 많은 아이 폰 4S 가 복무 하고 있 기 때문이다.아이 폰 6 는 문제 의 장비 로 보 유량 이 높 아 현재 아이 폰 6s 이전의 장비 점유 율 이 40%에 이 를 것 으로 추정 된다.
따라서 온라인 앱 에 카드 검사 도 구 를 추가 하려 고 시도 하면 카드 가 나타 날 확률 이 매우 높다 는 것 을 알 게 될 것 이다.그러나 카드 의 검 사 는 복구 가 쉽 지 않 은 것 은 개발 장비 에서 재현 되 기 어렵 기 때문이다.
이전에 메 인 스 레 드 카드 톤 모니터링 을 소개 하 는 글 을 쓴 적 이 있 습 니 다.현재 주류 의 방법 은 Runloop 사건 의 리 셋 을 감시 하고 리 셋 에 들 어 가 는 시간 간격 이 Threshold 를 초과 하 는 지,초과 하면 현재 App 의 모든 스 레 드 를 기록 하 는 call stack 인 것 같 습 니 다.
나 는 얼마 전에 백 스테이지 에 보 고 된 카드 로그 에서 이런 콜 스 택 을 보 았 다.
> 0 libsystem_kernel.dylib __workq_kernreturn
> 1 libsystem_pthread.dylib _pthread_workqueue_addthreads
> 2 libdispatch.dylib _dispatch_queue_wakeup_global_slow
> 3 libdispatch.dylib _dispatch_queue_wakeup_with_qos_slow
> 4 libdispatch.dylib dispatch_async
그 러 니까 카드 가 dispatch 에 나타 난 거 야.async,내 가 현재 GCD 에 대한 인식 으로 dispatchasync 는 절대로 멈 출 수 없다.dispatch_async 의 주요 임 무 는 시스템 스 레 드 탱크 에서 작업 스 레 드 를 꺼 내 블록 을 이 스 레 드 에 넣 어 수행 하 는 것 입 니 다.
상기 콜 stack 은 확실히 나 타 났 고 샘플 의 수량 이 적지 않 으 며 마지막 함 수 는 커 널 호출 이 분명 합 니 다.함수 이름 으로 추측 하면 GCD 가 스 레 드 탱크 에서 스 레 드 를 가 져 오 려 고 시 도 했 을 수도 있 지만 이미 스 레 드 가 실 행 된 상태 이기 때문에 시스템 커 널 에 새로운 스 레 드 를 만 들 기 를 신청 합 니 다.하지만 스 레 드 를 만 드 는 커 널 호출 이 느 릴 까요?메 인 스 레 드 가 멈 출 정도 로 느 릴 까요?의문 을 가지 고 나 는 관련 자 료 를 대량으로 검색 했다.마지막 으로 관련 된 글 이 있다.http://newosxbook.com/articles/GCD.html
그 중 에 이런 말 이 있다.
This isn't due to 10.9's GCD being different - rather, it demonstrates the true asynchronous nature of GCD: The main thread has yet to return from requesting the worker (which it does by pthread_workqueue_addthreads_np, as I'll describe later), and already the worker thread has spawned and is mid execution, possibly on another CPU core. The exact state of the main thread with respect to the worker is largely unpredictable.
저 자 는 GCD 가 신청 한 스 레 드 가 다른 작업 을 처리 하고 있 는 thread 일 수 있다 고 생각 합 니 다.main thread 는 이 바 쁜 스 레 드 가 돌아 와 야 계속 실 행 될 수 있다 는 것 에 의문 을 가지 고 있 습 니 다.
마지막 으로 도움 을 청 할 길이 없 는 상황 에서 나 는 귀중 한 TSL 기 회 를 이용 하여 애플 의 엔지니어 에 게 직접 가르침 을 청 하기 로 결정 했다.여기 서 애플 에 technical support 를 구 하 는 것 은 매우 귀중 하고 실행 가능 한 방안 이 며 개발 자 계 정 마다 매년 2 번 의 기회 가 있 으 므 로 매우 아 쉬 울 필요 가 없다.
저 는 문 제 를 던 진 후에 애플 커 널 엔지니어 의 대답 을 받 았 습 니 다.저 는 간단 한 대답 을 문답 형식 으로 보 여 드 리 겠 습 니 다.
Q: looks like even if it's async dispatching, the main thread still has to wait for the other thread to return, during which time, the other thread happen to be in mid execution of sth. this confuses me, what exactly is the main thread waiting for?
왜 메 인 스 레 드 는 dispatch 를 기 다 려 야 합 니까?async 복귀,메 인 스 레 드 는 도대체 무엇 을 기다 리 고 있 습 니까?
A: It's hard to say with just a user space backtrace. Frame 0 has clearly sent the current thread into the kernel, and this specific kernel call is /way/ too complex to analyse from outside [1].
사용자 상태 호출 스 택 에서 답 을 얻 을 수 없고 커 널 의 가능 한 상태 가 너무 복잡 합 니 다.
Q: I know it's suggested that we create limited amount of serial queue,and use target queue probably. but what could happen if we don't follow that rule?
애플 은 자신 이 serial GCD queue 를 만 들 때 반드시 수량 을 조절 하고 target queue 를 설정 하 는 것 이 좋 습 니 다.그렇지 않 으 면 문제 가 발생 할 수 있 지만 어떤 문제 가 발생 할 지 궁금 해서 이번 에는 기 회 를 빌려 함께 물 었 습 니 다.
A:

* On macOS, where the system is happier to over commit, you end up with a thread explosion. That in turn can lead to problems running out of memory, running out of Mach ports, and so on.

* On iOS, which is not happy about over committing, you find that the latency between a block being queued and it running can skyrocket. This can, in turn, have knock-on effects. For example, the last time I looked at a problem like this I found that `NSOperationQueue` was dispatching blocks to the global queue for internal maintenance tasks, so when one subsystem within the app consumed all the dispatch worker threads other subsystems would just stall horribly.

Note: In the context of dispatch, an “over commit” is where the system had to allocate more threads to a queue then there are CPU cores. In theory this should never be necessary because work you dispatch to a queue should never block waiting for resources. In practice it's unavoidable because, at a minimum, the work you queue can end up blocking on the VM subsystem.

Despite this, it's still best to structure your code to avoid the need for over committing, especially when the over commit doesn't buy you anything. For example, code like this:

group = dispatch_group_create();
for (url in urlsToFetch) {
  dispatch_group_enter(group);
  dispatch_async(dispatch_get_global_queue(…), ^{
    … fetch `url` synchronously …
    dispatch_group_leave(group);
  });
}
dispatch_group_wait(group, …);

is horrible because it ties up 10 dispatch worker threads for a very long time without any benefit. And while this is an extreme example ― from dispatch's perspective, networking is /really/ slow ― there are less extreme examples that are similarly problematic. From dispatch's perspective, even the disk drive is slow (-:
이 답장 은 매우 재미있다.GCD 소스 코드 를 읽 어 본 학생 들 은 모든 기본 값 으로 만 든 GCD quue 에 우선 순위 가 있다 는 것 을 알 게 될 것 입 니 다.그러나 모든 우선 순 위 는 두 개의 quue 에 대응 합 니 다.예 를 들 어 하 나 는 default-priority 이 고 다른 하 나 는 default-priority-overcommit 입 니 다.dispatch_async 에 서 는 먼저 default-priority 대기 열 에 작업 을 던 집 니 다.대기 열 이 가득 차 면 default-priority-overcommit 에 던 집 니 다.
Mac 시스템 에서 GCD 는 overcommit 를 허용 합 니 다.매번 dispatch 를 의미 합 니 다.async 는 새로운 스 레 드 를 만 들 것 입 니 다.over commt 가 되 더 라 도 이 과 다 한 스 레 드 는 우선 순위 에 따라 CPU 자원 을 경쟁 합 니 다.
iOS 시스템 에 서 는 GCD 가 overcommit 를 제어 합 니 다.어떤 우선 순위 대기 열 over commit 에 있 으 면 뒤에 있 는 작업 이 대기 상태 에 있 습 니 다.모 바 일 기기 의 CPU 자원 이 비교적 부족 한데 이런 디자인 은 상식 에 부합된다.
따라서 iOS 에 지나치게 많은 serial quue 를 만 들 면 다음 에 제출 한 작업 은 계속 대기 상태 일 수 있 습 니 다.이것 도 우리 가 queue 의 수량 과 등급 관 계 를 엄 격 히 통제 해 야 하 는 이유 입 니 다.가장 좋 은 것 은 App 에서 모든 서브 시스템 은 고정된 수량 과 우선 순위 의 queue 만 분배 하여 thread explosion 으로 인 한 코드 가 제때에 실행 되 지 않도록 하 는 것 입 니 다.
Q:I know the system watchdog can kill an app if the main thread is taking too long to respond. I also heard rumors that there are two other cases that may gets your app killed by watchdog. the first is too many new threads are being created like by random usage of dispatching work to global concurrent queue? the second case is if CPU has been kept too busy like 100% for too long, watchdog kills app too?
나 는 기 회 를 빌려 시스템 watchdong 이 앱 을 강하 게 죽 인 이 유 를 물 었 다.메 인 라인 이 장시간 반응 하지 않 는 것 외 에 너무 많은 스 레 드 와 CPU 가 장시간 과부하 로 돌아 가 는 것 도 강 살 될 수 있다 는 소문 이 돌 고 있 기 때문이다.
A:I'm not aware of any specific watchdog check along those lines, but it's not hard to imagine that the above-mentioned knock-on effects might jam up your app sufficiently for the watchdog to kill it for other reasons. Running the CPU for too long generates a crash report but it doesn't actually kill the app. It's essentially a ‘warning' crash report about the problem.
과도 한 스 레 드 를 만 드 는 것 은 watchdog 의 강 살 을 직접적 으로 초래 하 지 는 않 지만,과도 한 스 레 드 는 메 인 스 레 드 가 제때에 처리 되 지 못 하고 다른 이유 로 kill 에 의 해 처 리 될 수 있 습 니 다.한편,CPU 가 장시간 과부하 되 는 것 은 강 살 로 이 어 지지 않 지만 시스템 은 리 포트 를 생 성하 여 개발 자 에 게 경고 합 니 다.나 는 확실히 이런'this is not a crash'의 crash 로 그 를 많이 본 적 이 있다.
또 일부 문답 은 현재 나의 의문 과 직접적 으로 관련 되 지 않 기 때문에 생략 합 니 다.마지막 으로 재 미 있 는 답장 을 한 소절 더 붙 이 고 읽 기 전에 여러분 이 먼저 생각 하 셔 도 됩 니 다.

dispatch_async(myQueue, ^{
 // line A
});
// line B
라인 A 와 라인 B 는 누가 먼저 실행 합 니까?

Consider a snippet like this:

dispatch_async(myQueue, ^{
 // line A
});
// line B

there's clearly a race condition between lines A and B, that is, between the `dispatch_async` returning and the block running on the queue. This can pan out in multiple ways, including:

* If `myQueue` (which we're assuming is a serial queue) is busy, A has to wait so B will definitely run before A.

* If `myQueue` is empty, there's no idle CPU, and `myQueue` has a higher priority then the thread that called `dispatch_async`, you could imagine the kernel switching the CPU to `myQueue` so that it can run A.

* The thread that called `dispatch_async` could run out of its time quantum after scheduling B on `myQueue` but before returning from `dispatch_async`, which again results in A running before B.

* If `myQueue` is empty and there's an idle CPU, A and B could end up running simultaneously.
답안
사실 마지막 으로 저 는 제 가 원 하 는 정확 한 답 을 얻 지 못 했 습 니 다.답장 에서 말 한 것 처럼 상황 이 많 고 너무 복잡 해서 사용자 상태의 콜 스 택 을 통 해 커 널 의 상 태 를 간단하게 알 수 없 지만 가치 있 는 정 보 는 대체적으로 정리 할 수 있 습 니 다.
정보 1
iOS 시스템 자 체 는 자원 관리 와 배분 시스템 입 니 다.CPU,disk IO,VM 등 은 모두 희소 한 자원 입 니 다.각 자원 간 에 서로 영향 을 줄 수 있 습 니 다.메 인 스 레 드 의 카드 는 CPU 자원 에 병목 이 생 긴 것 처럼 보이 지만 커 널 은 다른 자원 을 관리 하 느 라 바 쁠 수도 있 습 니 다.예 를 들 어 현재 대량의 디스크 읽 기와 쓰기 가 발생 하거나 대량의 메모리 신청 과 청 소 를 하고 있 습 니 다.아래 의 간단 한 스 레 드 를 만 드 는 커 널 호출 이 걸 릴 수 있 습 니 다.

libsystem_kernel.dylib __workq_kernreturn
그래서 해결 방법 은 각 thread 의 call stack 을 스스로 분석 하고 사용자 장면 에 따라 현재 소모 되 고 있 는 시스템 자원 을 분석 할 수 밖 에 없다.뒤에 도 최근 에 제출 한 코드 분석 을 통 해 매우 오래 걸 리 는 디스크 io 작업(하위 스 레 드 에 두 었 지만)이 추가 되 어 별로 닿 지 않 는 콜 stack 이 나타 난 것 을 발견 했다.revert 이후 카드 경보 가 사 라 졌 습 니 다.
정보
기 존의 카드 검사 도 구 는 모두 시간 을 초과 한 상태 에서 만 dump call stack 이 발생 할 수 있 지만 시간 을 초과 한 것 은 임무 A,B,C 의 공동 작용 으로 인 한 것 일 수 있 습 니 다.A 와 B 는 진정 으로 시간 을 소모 하 는 임무 일 수 있 습 니 다.C 는 시간 을 소모 하지 않 지만 공교롭게도 마지막 이기 때문에 원흉 으로 여 겨 졌 지만 A 와 B 는 보고 로그 에 나타 나 지 않 았 습 니 다.나 는 당분간 특별히 좋 은 해결 방법 을 생각 하지 못 했다.분명,libsystemkernel.dylib __workq_kernrreturn 은 시간 이 별로 걸 리 지 않 는 C 작업 입 니 다.
정보
GCD 를 사용 하여 queue 를 만 들 거나 하나의 App 내부 에서 GCD 를 사용 하여 하위 스 레 드 작업 을 수행 할 때 모든 팀 이 지 킬 수 있 는 대기 열 사용 체 제 를 사용 하여 과도 한 thread 를 만 들 지 않도록 하 는 것 이 좋 습 니 다.예상 치 못 한 스 레 드 자원 이 부족 하고 코드 가 제때에 실행 되 지 못 하 는 상황 이 발생 합 니 다.특히 대기업 이 움 직 이면 백 명 이 넘 는 팀 에 서 는 어렵다.
총결산
이상 은 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 참고 학습 가치 가 있 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 셔 서 저희 에 대한 지지 에 감 사 드 립 니 다.

좋은 웹페이지 즐겨찾기