Blocking
제어형 언어는 명령을 메모리에 적재하고 순차적으로 실행하기 때문에 사실 상 언제나 cpu를 blocking하고 있습니다.
일단 blocking이 뭔지 자세히 설명하자면 한 마디로 컴터가 그걸 실행하는 동안 먹통이 되는 상태를 말합니다. 비슷한 말로는 동기화(sync) 로직이라고도 합니다.
하지만 현대의 OS들은 시분할 방식으로 cpu 제어권을 강제 탈환할 수 있기 때문에 멀티태스킹환경에서는 blocking제어명령을 보내도 빠져나올 수 있습니다. 따라서 많은 프로그래밍기법에서 thread를 활용하여 blocking명령을 다수 처리하거나 관리하는 기법을 사용합니다.
싱글쓰레드 기반으로 구성된 avm2의 경우 강제적인 시분할 타이밍을 만들어뒀는데 그게 바로 enterFrame으로 대표되는 frame시스템입니다. frame은 마치 쓰레드처럼 주기적으로 반복되는 실행타이밍인데 os가 제어권을 갖고 있지 않고 avm2가 제어권을 갖고 있기 때문에 한계가 명확합니다.
즉 한 프레임에서 긴 시간을 점유하는 blocking명령을 내리면 다음 프레임으로 강제로 넘길 수 없고 blocking이 풀릴 때까지 기다려야 한다는 거죠.
따라서 avm2의 흐름대로 프로그래밍 효과를 최대한 내고 싶다면 한 프레임이 실행되는 주기에 최대한 blocking 시간을 짧게 만들 필요가 있습니다.
어려운 용어는 이쯤하고 실제 blocking코드를 간단히 보죠.
for( i = 0 ; i < 999999 ; ++i ){
Math.random();
}
컴터 성능에 따라 다소간 차이는 있겠지만 이거 엄청나게 오랜 시간동안 컴터를 먹통으로 만듭니다.
이러한 명령이 실행되면 실행이 완료될 때까지 다음 프레임으로 넘어가지 못합니다. 플래시는 화면업데이트나 이벤트처리, 다른 로직의 처리 등을 전부 프레임주기로 갱신하기 때문에 다음 프레임으로 넘어가지 못하면 플래시 그 자체가 먹통이 됩니다.
게다가 script실행 환경 상 타임아웃제한이 있습니다. 위의 코드를 실행하여 30초가 넘어가면 더 이상 실행되지 못하고 다운됩니다. 따라서 blocking을 분산하는 기술은 대규모 프로그래밍의 필수적인 요소입니다.
제어형 언어에서 blocking을 일으키는 경우는 다양하게 있지만 이번 포스트에서는 loop만 다루도록 하겠습니다.
enterFrame
일단 위의 blocking로직을 non blocking으로 만들려면 잘게 쪼개 enterFrame에 태워야합니다. enterFrame을 사용하려면 최소한의 displayObject가 필요합니다. 가벼운 객체인 shape를 이용해 enterFrame을 준비하겠습니다.
var looper:Shape = new Shape;
다음은 전역 카운터가 필요합니다. 위의 for에서 999999에 해당되는 거라 할 수 있죠.
var counter:int;
또한 한 프레임에서 루프를 돌 제한도 알아야합니다. 즉 999999를 한프레임에 얼마나 쪼개서 돌릴 것인가라는거죠. 예를들어 300번씩만 돌린다면 999999/300 = 3334 프레임으로 돌아야 전체 루프가 완성될 것입니다. 초당 60프레임으로 돌린다면 프레임당 딜레이가 없다고 가정할 때 56초가 걸리게 됩니다.
var frameLimit:int;
다음은 루프의 몸체에 들어갈 함수와 그 함수에 전달한 인자입니다만, 보통 루프몸체의 로직은 루프 카운터를 활용하는 경우가 대부분 이므로 counter도 알 수 있게 해줘야 합니다.
var runner:Function; var param:Array;
이제 조각이 다 모였으니 간단한 클래스로 일반화 시킬 수 있습니다.
class nonBlockingLoop{
var looper:Shape = new Shape;
public function nonBlockingLoop(){}
public function loop( $counter:int, $limit:int, $runner:Function, ...$param ):void{
var counter:int;
looper.addEventListener( Event.ENTER_FRAME, function( $e:Event ):void{
var i:int;
while( i++ < $limit ){
if( counter < $counter ){
//$param.unshift( counter ); 필요하면 counter도 넘...
if( $param.length ){ //rest인자는 null인 경우가 없음
$runner.apply( null, $param );
}else{
$runner();
}
++counter;
}else{
looper.removeEventListener( $e.type, arguments.callee );
}
}
});
}
}
counter를 클로저로 선언했기 때문에 loop라는 함수는 동시에 몇 번이라도 불러도 잘 작동합니다.
var looper:nonBlockingLoop = new nonBlockingLoop;
looper.loop( 999999, 300, Math.random );
looper.loop( 999999, 300, getDefinition, 'flash.display.Sprite' );
function test( $val1:String, $val2:int ):void{
trace( $val1, $val2 );
}
looper.loop( 999, 20, test, '안녕', 37 );
결론
xml파싱을 비롯하여 무거운 blocking로직은 반드시 non blocking로직으로 고쳐써야 프로그램의 다운을 막을 수 있습니다.
또한 non blocking loop는 화면의 갱신과 다른 이벤트의 진행을 동시에 시켜주기 때문에 유저 입장에서 훨씬 좋은 응답성을 느끼게 되고 결과적으로 더 나은 이용 경험을 하게 됩니다.
최근 sng등에서 더욱 하드하게 플래시를 사용하는 이상 점점 더 필수적이 되어간다 할 수 있죠.
과제
- non blocking loop가 완료될 때 완료이벤트를 수신할 수 있도록 개선하시오.
- 아예 Shape를 상속하도록 개선하시오.
- 일반화시켜 클래스로 정리하는 포스팅을 한 건 처음이지만 non blocking에 대해서는 이미 두어 개의 포스트가 블로그 내에 존재하니 찾아보세요.



무거운 로직을 실행시킬때 먹통이 되는 현상을
이런식으로 해결할수 있다는 사실을 처음알았네요
올려주신 코드로 테스트도 해보았는데 잘돌아가네요,
많은걸 배워갑니다, 감사합니다!