본문 바로가기

Python Schedule 라이브러리

Python Schedule라이브러리에 대한 실험 결과다.

자세한 사용법은 아래 docs에 자세하게 나와있다.

 

 

schedule — schedule 1.1.0 documentation

 

schedule.readthedocs.io

 

1. 스케줄러

application 레이어에서의 스케줄러란, 특정 작업을 원하는 주기로 실행시키기 위한 툴이다.

스케줄러의 스케줄링에는 크게 두종류가 있다.

 

1. 원하는 주기로 작업 실행 

작업 종료후, 원하는 시간만큼 기다렸다가 다음 작업을 실행하는 경우다.

ex) 5초 주기로 실행

2. 원하는 시간에 작업 실행 

특정 시간에 작업을 실행하는 경우다

ex) 매 정각에 실행 (결국 1시간 주기로 실행)

 

이 두가지 스케줄링 방법에 대해 스케줄러가 어떻게 작업을 배치하는지에 대해 알아보는 포스팅이다.

 

실험해볼 작업은 총 3가지 작업에 대해 알아보려 한다.

1. 작업 시간이 0에 수렴하는 작업 

2. 작업시간이 스케줄 주기보다 작은 작업

3. 작업시간이 스케줄 주기보다 큰 작업

 

이제 스케줄링 방법과 작업에 따라 6가지로 나뉘어 살펴본다.

 

2. 실험

 

2.1 작업시간이 0에 수렴하는 작업과, 특정 주기로 작업 실행

import schedule, datetime, time

def job():
    print(f'작업 시작 : {datetime.datetime.now()}')
    print(f'작업 종료 : {datetime.datetime.now()}')

schedule.every(5).seconds.do(job)

while True:
    n = schedule.idle_seconds()
    if n is None:
        break
    elif n > 0:
        time.sleep(n)
    schedule.run_pending()

작업 수행시간은 0초, 스케줄 주기는 5초다.

 

작업시간 0, 매 5초마다 실행

결과는 예상했던것과 같이 매 5초마다 작업이 시작되고 바로 종료된다.

 

2.2 작업 시간이 스케줄 주기보다 작고, 특정 주기마다 작업 실행

import schedule, datetime, time

def job():
    print(f'작업 시작 : {datetime.datetime.now()}')
    time.sleep(3)
    print(f'작업 종료 : {datetime.datetime.now()}')

schedule.every(5).seconds.do(job)

while True:
    n = schedule.idle_seconds()
    if n is None:
        break
    elif n > 0:
        time.sleep(n)
    schedule.run_pending()

 

첫번째 작업 시작 시간은 16분 52초이고, 첫번째 작업 종료는 16분 55초다.

하지만 두번째 작업은 첫번째 작업의 시작 시간이 아닌 작업 종료 시간을 기준으로 5초의 주기를 가진 후 실행된다.

 

 

작업시간 3초 , 스케줄 주기 5초

 

2.3 작업시간이 스케줄 주기보다 크고, 특정 주기마다 작업 실행.

import schedule, datetime, time

def job():
    print(f'작업 시작 : {datetime.datetime.now()}')
    time.sleep(6)
    print(f'작업 종료 : {datetime.datetime.now()}')

schedule.every(5).seconds.do(job)

while True:
    n = schedule.idle_seconds()
    if n is None:
        break
    elif n > 0:
        time.sleep(n)
    schedule.run_pending()

 

결과는 2.2와 마찬가지로 종료 이후 5초 대기후 다음 작업이 실행된다.

 

작업시간 6초, 스케줄 주기 5초

 

2.4 작업시간 0초, 특정 시간마다 작업 수행 (hh:mm:00, hh:mm:05, hh:mm:10, ... ,hh:mm:55)

import schedule, datetime, time

def job():
    print(f'작업 시작 : {datetime.datetime.now()}')
    # time.sleep(0)
    print(f'작업 종료 : {datetime.datetime.now()}')

# 매분 0초, 5초, 10초, ... , 55초에 작업이 실행되도록 스케줄링
for s in range(0, 60, 5):
    s = str(s).zfill(2)
    schedule.every().minute.at(f':{s}').do(job)

while True:
    n = schedule.idle_seconds()
    if n is None:
        break
    elif n > 0:
        time.sleep(n)
    schedule.run_pending()

 

결과는 설정한 매 시간 (0초, 5초, 10초, ...) 마다 작업이 실행되며 밀리지 않는다.

작업시간 0초, 특정 시간마다 수행

 

2.5 작업시간이 스케줄 주기보다 작고, 특정 시간마다 수행

import schedule, datetime, time

def job():
    print(f'작업 시작 : {datetime.datetime.now()}')
    time.sleep(3)
    print(f'작업 종료 : {datetime.datetime.now()}')

# 매분 0초, 5초, 10초, ... , 55초에 작업이 실행되도록 스케줄링
for s in range(0, 60, 5):
    s = str(s).zfill(2)
    schedule.every().minute.at(f':{s}').do(job)

while True:
    n = schedule.idle_seconds()
    if n is None:
        break
    elif n > 0:
        time.sleep(n)
    schedule.run_pending()

작업의 종료시간과 상관없이 작업의 시작은 설정된 스케줄 시간에 시작된다.

 

 

작업시간 3초, 특정 시간마다 수행

 

2.6 작업시간이 스케줄 주기보다 크고, 특정 시간마다 수행

사실 이게 궁금해 진행한 실험들이였다.

import schedule, datetime, time

def job():
    print(f'작업 시작 : {datetime.datetime.now()}')
    time.sleep(6)
    print(f'작업 종료 : {datetime.datetime.now()}')

# 매분 0초, 5초, 10초, ... , 55초에 작업이 실행되도록 스케줄링
for s in range(0, 60, 5):
    s = str(s).zfill(2)
    schedule.every().minute.at(f':{s}').do(job)

while True:
    n = schedule.idle_seconds()
    if n is None:
        break
    elif n > 0:
        time.sleep(n)
    schedule.run_pending()

결과는 아래 사진과 같다.

첫번째 작업의 시작은 스케줄링 대로 45초에 시작하였고 첫번째 종료는 51초였다.

이후 스케줄링된 시간인 50초나 55초에 시작하는것이 아닌 종료된 51초에 바로 시작하는것을 볼 수 있다.

 

그림으로 보면 다음과 같다.

스케줄 시간과 상관없이 이전 작업이 끝날때 다음 작업이 시작된다

 

나는 작업이 언제 종료될지 알 수 없기 때문에, 작업의 종료와 상관없이 정해진 시간에 스케줄링 되기를 원했고,
공식 문서에 다음과 같은 예시가 있어 가져왔다.

 

2.7 작업 종료와 상관없이 특정 시간에만 실행

멀티 스레드를 통해 병렬로 여러 작업들을 수행하는 방법으로, 이전 작업의 종료 여부와 상관없이 스케줄링된 시간이 된다면 다음 작업이 실행된다.

 

import schedule, datetime, time, threading

def job():
    print(f'{threading.current_thread()} | 작업 시작 : {datetime.datetime.now()}')
    time.sleep(6)
    print(f'{threading.current_thread()} | 작업 종료 : {datetime.datetime.now()}')

def run_thread(job_func):
    t = threading.Thread(target=job_func)
    t.start()

# 매분 0초, 5초, 10초, ... , 55초에 작업이 실행되도록 스케줄링
for s in range(0, 60, 5):
    s = str(s).zfill(2)
    schedule.every().minute.at(f':{s}').do(run_thread, job)

while True:
    n = schedule.idle_seconds()
    if n is None:
        break
    elif n > 0:
        time.sleep(n)
    schedule.run_pending()

결과는 아래와 같다.

작업시간 6초, 멀티 스레드 실행

첫번째 작업은 쓰레드1 에서 20초에 시작되고, 종료 예정 시간은 26초다.

하지만 25초가 되는 시점에서 다른 쓰레드2에서 두번째 작업이 실행되고,

이후 26초에 첫번째 작업은 종료된다.

 

그림으로 보면 다음과 같다.

 

즉 이전 작업의 종료와 상관없이 다음 작업이 다른 스레드에서 실행되므로 작업 수행을 보장받을 수 있다.

 

단! 작업이 종료되지 않으면 스레드가 무한정으로 많아져 추가적인 관리가 반드시 필요하다!!