파이썬에서 스레드 사용하기 – threading

스레드는 프로그램이 실행되는 실행 흐름의 최소 단위이다. 어떤 프로그램이 실행되면 기본적으로 해당 프로그램을 위한 프로세스가 생성된다. 그리고 다시 이 프로세스는 하나의 스레드를 만들고 (이것이 해당 프로세스의 메인 스레드가 된다.) 이 스레드를 따라 코드가 실행된다.

하나의 프로세는 한 개 이상의 스레드를 동시에 실행시킬 수 있다. 이 말은 메인 루틴이 진행하는 동안 병렬적으로 다른 함수들이 같이 실행될 수 있다는 말이다. 스레드는 프로세스에 종속되므로 프로세스 내에서 스레드가 추가로 만들어질 때 이 새로운 스레드는 프로세스의 코드와 메모리를 공유한다. (반대로 멀티프로세스는 각각 독립된 코드 및 메모리 영역을 가지고 돌아간다.) 스레드는 이처럼 동시에 같은 작업들을 처리하여 전체적인 성능을 향상시키거나 루틴의 흐름을 중단시키지 않고 별개의 작업 흐름이 서브 루틴을 실행하여 서로 다른 작업을 함께 진행할 때 사용한다.

파이썬에서 스레드를 사용할 수 있도록 해주는 모듈로 _threadthreading이 있다. _thread 모듈은 저수준의 API를 제공하고 있고, 이를 기반으로 고수준 API를 제공하는 threading 모듈이 있다. 이 글에서는 threading 모듈을 사용하여 스레드를 생성, 실행하고 락, 세마포어 등의 동기화 수단을 사용하여 실행 흐름을 제어하는 방법을 살펴보겠다.

파이썬에서 스레드 사용하기 – threading 더보기

[iOS/OSX] Timer 사용하기

특정한 시간 이후에 작업을 시행하는 타이머를 사용하는 방법에는 몇 가지 방법이 있을 수 있는데, 코코아에서 타이머는 NSTimer 객체를 통해 구현된다.

타이머와 런루프

타이머는 런루프(Run Loop)와 밀접한 관련을 맺고 있다. 타이머는 스스로 동작기한을 가지고 있고, 그 상태로 런루프에 등록된다. 런루프는 그 기한이 지나는 시점에 타이머를 지켜보고 있다가, 타이머가 실행하기로 한 액션을 지정된 객체로 보내게 된다.

런루프는 (역시나 런루프와 스레드를 혼동하는 불편한 진실 때문에 이에 대한 별도의 포스팅도 필요할 듯) 사용자의 입력 등에 적절히 반응하기 위해서 계속해서 “기다리는” 일종의 루프이다. 예를 들면 키보드는 풀링이라는 방식으로 일종의 런루프를 가지고 있다. 즉 사용자가 키보드를 누르지 않는 상황에서 키보드는 ‘최선을, 총력을 다해서’ 사용자 입력을 반복해서 기다린다. 런루프는 이와 유사하게 스레드 내에서 사용자의 UI 입력을 최선을 다해 기다린다.

타이머가 지정한 액션은 런루프에 의해서 확인된다. 타이머를 사용할 때 가장 유의해야 하는 점은 NSTimer는 실시간 타이머가 아니라는 점이다. 만약 반복되는 주기를 가진 타이머의 특정 작업이 있다고 할 때, 런루프는 단지 그 타이머만을 감시하는 것이 아니라 아주 많은 종류의 입력을 처리해야 하기 때문에 이를 놓치는 경우가 있을 수 있다. 따라서 NSTimer를 통해 이뤄지는 지연된 작업이나, 반복작업은 실제 시간과 다소 차이가 있을 수 있으며, 경우에 따라서는 “상당히 큰” 차이가 발생할 수 있다.

타이머 사용하기

스케줄된 타이머 사용하기

타이머를 사용하는 가장 일반적인 방법은 NSTImer의 scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 를 사용하는 방법이다. 이는 현재 메인 런루프에 타이머를 추가한다.

-(void)startOneOffTimer {
    [NSTimer scheduledTimerWithTimeInterval:1.0
        target:self
        selector:@selector(timerFired:)
        userInfo:[self userInfo]
        repeat:NO];
}

 

-(void)startRepeatingTimer {
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.5
        target:self selector:@selector(timerFired:)
        userInfo:[self userInfo] repeat:YES];
    self.repeatedTimer = timer;
}

타이머의 반복이 없는 경우에는 타이머가 종료된 후 해당 액션을 호출해주고 난 후 타이머는 자체적으로 해제된다. 그렇지 않은 경우에는 추후에 반복되는 타이머를 해제해주기 위해서 따로 참조점을 갖고 있어야 한다.

스케줄링 된 타이머는 생성 즉시 런루프에 추가되어 카운트다운을 시작하게 된다. 이와 별도로 특정 시점부터 동작을 시작하는 타이머는 다음과 같이 만들 수 있다.

-(void)startFireDateTimer {
    NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:3600];
    NSTimer *timer = [[NSTimer alloc]
        initWithFireDate:fireDate
        interval:0.5
        target:self
        selector:@selector(timerFiredMethod:)
        userInfo:[self uesrInfo]
        repeat:YES];
    NSRunLoop *theRunLoop = [NSRunLoop currentRunLoop];
    [theRunLoop addTimer:timer forMode:NSDefaultRunLoopMode];
}

시작된 타이머를 멈추기

위와 같이 만들어지고 시작된 타이머는 반복되지 않는다면 종료시 호출해야 하는 액션을 호출하고, 정지된다. 하지만 아직 정해진 시간을 다 채우지 않았거나, 반복해서 실행되는 타이머는 멈출 필요도 있다. 타이머를 멈추기 위해서는 해당 타이머에 invalidate 메시지를 보내면 된다.

-(void)stopRepatedTImer
{
    [self.repeatedTimer invalidate];
    self.repeatedTimer = nil;
}

타이머를 사용하지 않은 예약된 작업 호출

타이머를 사용하지 않고도 특정한 시간 지연 후에 간단히 어떤 기능을 실행할 수 있다. 이 기능은 NSObject에 이미 정의된 메소드이다. (역시 코코아의 루트 클래스가 NSObject라는 사실은 매우 다행스럽다!) 이는 -performSelector: withObject : afterDelay: 이다. 이는 다음과 같이 사용할 수 있다.

    
[self.tableView performSelector:@selector(reloadData)
    withObject:nil
    afterDelay:10.0];

이미 예약된 작업 취소하기

NSObject의 클래스 메소드인 +cancelPreviousPerformRequestsWithTarget:selector:object:+cancelPreviousPerformRequestsWithTarget: 을 사용한다. 이는 지연 후에 일을 하게되는 객체가 이 메시지를 받고 예약된 작업을 실행하지 않게 된다.