12.6.6 스레드와 스택(Threads And Stacks)
12.6.6 스레드와 스택(Threads And Stacks)
보수적, 협조적(Conservative, co-operation)
hxcpp은 보수적인 stop-the-world GC를 사용하며, 스레드가 협조해야 합니다.
- 스레드는 컬렉션 단계에서 GC 포인터를 변경해선 안 됩니다.
- 스레드 스택/레지스터는 GC 포인터에 대해 스캔되어야 합니다.
- 스레드는 GC 시스템에 그들을 기다리지 말라고 알리지 않고 블록하면 안 됩니다. 그렇지 않으면 GC가 블록이 끝날 때까지 기다립니다.
- 잠재적으로 블록하는 시스템 호출(파일시스템, 네트워크 등) 전에 Haxe에서
hx::GCEnterBlocking()/gc_enter_blocking()/cpp.vm.Gc.enterGCFreeZone()를 호출하세요. - 더 많은 GC 호출을 하기 전에 Haxe에서
hx::GCExitBlocking()/gc_exit_blocking()/cpp.vm.Gc.exitGCFreeZone()를 호출하세요. - 버퍼를 미리 할당해야 할 수도 있습니다.
- 오류 조건에서 블록을 빠져나가는 것을 잊지 마세요.
외부 스레드(Foreign Threads)
Haxe에서 스레드를 만들면 연결(attached)된 상태로 시작합니다. Haxe가 만든 것이 아닌 스레드가 hxcpp과 상호작용하기 전에 몇 가지 주의가 필요합니다. GC 할당은 스레드당 GC 컨텍스트로 수행되고 모든 스레드가 중지된 세계(stopped world)를 존중해야 하기 때문입니다.
외부 스레드는 연결-분리(attached-detached)되어야 합니다:
SetTopOfStack(int * inTop,bool inPush)inTop- 연결할 스택 상단에 대한 포인터, 또는 스택을 제거하려면0.inPush- 보통true. 재귀적 연결/분리.
세계가 중지되었을 때는 변경하면 안 됩니다.
스캔을 위해 자신의 스택 범위를 정의해야 합니다.
연결되었다면 GC-free zone에 들어가고 나올 필요가 있을 수 있습니다.
더 이상 호출할 것이 없으면 완료 시 컨텍스트를 해제해야 합니다.
로컬 변수가 스택에 포함되는지 확인하세요.
- 컴파일러가 재정렬할 수 있으므로 주의하세요.
오디오 콜백 같은 일부는 다른 스레드에서 발생하므로 문서를 읽으세요.
다른 기법을 사용할 수 있습니다. 예:
- 신호를 기다리며 블록하는 Haxe 스레드를 만듭니다.
- 외부 스레드가 요청을 생성하고 Haxe 스레드에 신호를 보냅니다.
- Haxe 스레드가 작업을 수행하고 데이터를 생성한 다음 외부 스레드에 신호를 보냅니다.
- 외부 스레드가 데이터를 집어 들고 계속 진행합니다.
스택 상단(Top of Stack)
스레드를 다루는 방법을 이해하려면 C++ 스택의 정신적 그림이 필요합니다.
스택은 보통 "아래로" 자랍니다. 즉 첫 번째 스택 위치가 10000이면 다음은 9999 등입니다.
역사적으로 일관됩니다. emscripten은 위로 가지만 예외입니다 - 그러나 같은 용어/그림을 사용하고 코드에서 작음은 큼으로 바꾸면 됩니다.
시스템이 각 프로그램 스택을 10000에서 시작한다고 하면 로컬 변수와 인자가 스택에 푸시되면서 스택은 다음과 같이 보일 수 있습니다:
10000
-----------------------------------------------
9996 startup temp variable
9992 startup temp variable
-- main function --
9988 main return address - order and details of this are ABI specific
9984 char ** argv
9980 int argc
그런 다음 hxcpp은 메인 코드를 실행하며, 이는 HX_TOP_OF_STACK 매크로로 시작하고, 이 매크로는 다음과 같이 확장됩니다:
int t0 = 99;
hx::SetTopOfStack(&t0,false);
...
__boot_all();
__hxcpp_main();
-- main function --
9988 main return address order and details of this are ABI specific
9984 char ** argv
9980 int argc
9976 int t0
-- hx::SetTopOfStack --
records '9976' as top of stack for this thread
나중에 많은 생성된 함수 깊이에서 __hxcpp_main은 컬렉션을 촉발하는 할당 호출을 생성합니다.
...
8100 Array bullets
-- alloc Enemy --
...
-- Call collect --
8050 int bottomOfStackTemp
MarkConservative(&bottomOfStackTemp, 9976) -> scans stack from 8050 -> 9976
MarkConservative(Capture registers)
들기/나기(enter/exit)는 비슷한 기법을 사용하며, 여기서 레지스터가 캡처되고 "GC-free zone 들어가기" 호출이 이루어질 때 bottomOfStack이 '고정(locked-in)'됩니다.
8100 Array bullets
-- EnterGCFreeZone --
8088 int bottomOfStackTemp
thread->setBottomOfStack(&bottomOfStackTemp)
thread->captureRegisters()
return
* any changes here will not affect GC
이제 다른 스레드가 컬렉션을 수행할 때, GC-free 스레드는 스택 아래에서 무슨 일이 일어나든 8088에서 9976까지 스캔될 수 있습니다.
Main에서 호출되지 않음(Not Called From Main)
GUI 프레임워크가 실제 "main"이 없을 때 스택 상단을 올바르게 잡는 것은 까다로울 수 있습니다.
10000
-----------------------------------------------
9996 startup temp variable
9992 startup temp variable
-- main function --
setupWindows(onReadyCallback)......
...
8000
-- onReadyCallback --
7976 int t0
SetTopOfStack(&t0,false) -> 7966
__hxcpp_main();
setOnFrameCallack(haxeOnFrame)
return;
나중에 haxeOnFrame 콜백이 촉발되지만 __hxcpp_main "아래"가 아닙니다.
9800 -- haxeOnFrame ---
// Top of stack will be below bottom of stack.
해결책:
- main의 상단에 들어가도록 하세요 - 너무 많이 스캔할 수도 있습니까?
- 콜백에서 스택 상단을 올리세요,
inForce = false:gc_set_top_of_stack(void * inTopOfStack,bool inForce);
hxcpp_main후에 메인 스레드를 분리하고 각 콜백에서 다시 연결하세요:- Android 해결책은 렌더 콜백이 다른 스레드에서 발생하기 때문입니다.
gc_set_top_of_stack(&base,true);- 연결gc_set_top_of_stack(0,true);- 분리
디버깅(Debugging.)
디버그 모드에서 hxcpp은 연결되지 않은 스레드의 호출을 확인합니다.
hxcpp은 보수적 범위를 기록할 수 있습니다. 네이티브 디버거로 로컬 변수의 주소를 확인하고 포함되는지 확인할 수 있습니다.
hxcpp은 스택의 네이티브 객체를 스캔하지만 다른 객체에 대한 비-Haxe 포인터는 따르지 않으므로, 추가 GC 루트가 필요할 수 있습니다.