Thread Example

|

Python의 Thread

GIL

대부분의 프로그램에서 Thread는 아주 많이 활용되고 있습니다. 하지만, Python의 Thread는 제약이 있습니다. GIL(Global Interpreter Lock)이라고 불리우는 전역 인터프리터 락이 존재합니다. GIL 때문에 여러 개의 코어(Core)를 갖고 있는 CPU에서 프로그램을 실행하더라도 단 하나의 코어만 활용하도록 되어 있습니다. 그 덕분에 Thread를 사용하게 되면 Thread를 사용하지 않았을 때보다 성능이 훨씬 더 저하되는 현상이 발생합니다.

한 동안 GIL을 제거하려는 논의가 있었지만, GIL은 여전히 존재하고 있습니다. GIL을 제거했을 때 얻는 장점과 추가로 생기는 단점의 큰 차이가 없어서 GIL을 유지하는 것으로 결정되었다고 합니다.

GIL은 CPU 기반의 작업에서만 아주 크게 영향을 미칩니다. I/O 작업(대부분의 시스템 콜(System Call)이나 네트워크 작업들)에서는 Thread를 사용할 경우 성능적인 장점이 있습니다. 시스템 콜을 하게 될 경우 그 순간 GIL은 해제되어 다른 작업을 할 수 있게 하고, 시스템 콜이 끝나면 다시 GIL을 획득하도록 되어 있어 Thread로 성능 향상을 가져올 수 있습니다.

그 외 CPU 기반의 다중 작업을 해야 하는 경우는 Thread 보다는 멀티프로세스(Multi Process)를 활용하는 것이 좋습니다.

Thread를 이용하는 방법은 여러가지가 있는데 그 중 threading 모듈을 이용해서 사용하는 것을 추천합니다. 저수준의 _thread 모듈과 고수준의 threading 모듈이 있는데 threading 모듈만 이용해도 대부분의 작업을 처리할 수 있으며 다뤄야 할 부분이 훨씬 적기 때문에 간단하고 안전하게 사용할 수 있기 때문입니다.


특정 함수를 Multi-Threading으로 실행

import threading
import time


def loop(id):
    print('Thread({}) is starting...'.format(id))

    for i in range(10):
        print('Thread({}) - {}'.format(id, i))
        time.sleep(0)

    print('Thread({}) is finished...'.format(id))


threads = []

if __name__ == '__main__':
    for i in range(5):
        th = threading.Thread(target=loop, args=(i,))
        threads.append(th)
        th.start()

    for th in threads:
        th.join()

    print('<End>')


특정 클래스를 Multi-Threading으로 실행

import threading
import time

class MyThread(threading.Thread):
    def run(self):
        print('Thread({}) is starting...'.format(self.getName()))

        for i in range(10):
            print('Thread({}) - {}'.format(self.getName(), i))
            time.sleep(0)

        print('Thread({}) is finished...'.format(self.getName()))

threads = []

if __name__ == '__main__':
    for i in range(5):
        th = MyThread()
        threads.append(th)
        th.start()

    for th in threads:
        th.join()

    print('<End>')


Thread Lock

import threading
import time

count = 0

class MyThread(threading.Thread):
    def run(self):
        global count

        for i in range(10):
            lock.acquire()
            count += 1
            lock.release()

threads = []
lock = threading.Lock()

if __name__ == '__main__':
    for i in range(5):
        th = MyThread()
        threads.append(th)
        th.start()

    for th in threads:
        th.join()

    print('Total Count: {}'.format(count))

만약 acquire() 메소드를 여러 번 호출하려면 Lock 대신 RLock 클래스를 활용하면 됩니다. 물론, 해제할 때는 acquire() 메소드 호출 횟수만큼 release()를 호출해야합니다.

TCP Echo Server/Client

|

Echo Server

Python 3.x 에서 동작하는 TCP 기반의 Echo Server/Client 예제 코드입니다.

import socketserver
import sys

class TcpEchoServerHandler(socketserver.BaseRequestHandler):
    def handle(self):
        print('Client is connected : {0}'.format(self.client_address[0]))
        sock = self.request

        buffer = sock.recv(1024)
        received_message = str(buffer, encoding='utf-8')
        print('Received : {0}'.format(received_message))
        sock.send(buffer)
        sock.close()

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('{0} [Bind IP]'.format(sys.argv[0]))
        sys.exit()

    bindIP = sys.argv[1]
    bindPort = 10070

    server = socketserver.TCPServer((bindIP, bindPort), TcpEchoServerHandler)

    print("Start Echo-Server")
    server.serve_forever()


Echo Client

import socket
import sys

serverPort = 10070

if __name__ == '__main__':
    if len(sys.argv) < 4:
        print('{0} [BindIP] [Server IP] [Message]'.format(sys.argv[0]))
        sys.exit()

    bindIP = sys.argv[1]
    serverIP = sys.argv[2]
    message = sys.argv[3]

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((bindIP, 0))

    try:
        sock.connect((serverIP, serverPort))

        buffer = bytes(message, encoding='utf-8')
        sock.send(buffer)
        print('Sended message : {0}'.format(message))

        buffer = sock.recv(1024)
        received_message = str(buffer, encoding='utf-8')
        print('Received message : {0}'.format(received_message))

    finally:
        sock.close()

정규식 사용하기

|

Python에서 re 모듈은 정규 표현식(Regular Expression)에 관련된 라이브러리입니다.

^$ 연산자를 사용해서 각 줄의 시작과 끝을 매칭 가능하며 대소문자를 무시하는 re.I 플래그나 여러 줄로 이루어진 텍스트에 적용할 수 있는 re.M 플래그를 많이 사용합니다.

정규식 문법

기본 연산자

연산자 | 설명 —|— . | 개행 문자를 제외한 모든 문자 a | 문자 a ab | 문자열 ab x|y x나 y \y | 특수문자(^+{}$()[]|-?.* 등)에서 탈출


문자열 클래스

연산자 설명
[a-d] a 부터 d 까지 중 문자 1개
[^a-d] a, b, c, d를 제외한 문자 1개
\d 숫자(digit) 1개
\D 숫자가 아닌 개체 1개
\s 공백 1개
\S 공백이 아닌 개체 1개
\w 알파벳 또는 숫자 1개
\W 알파벳이나 숫자가 아닌 개체 1개


양적 연산자

연산자 설명
x* 0개 이상의 x
x+ 1개 이상의 x
x? 0 또는 1개의 x
x{2} x가 정확히 2개
x{2, 5} x가 2개부터 5개까지


탈출 연산자

연산자 설명
\n 새로운 라인(개행 문자)
\r 캐리지 리턴(현재 줄의 맨 앞)
\t 탭(tab)


위치 지정

연산자 설명
^ 문자열의 처음
\b 단어 경계
\B 비단어 경계
$ 문자열의 끝


그룹

연산자 설명
(x) 캡처링 그룹(capturing group)
(?:x) 비캡처링 그룹(non-capturing group)


정규식 예제

이메일 주소

r"\w[-\w\.]*@\w[-\w]*(\.\w[-\w]*)+"

HTML 태그

r"<TAG\b[^>]*<(.*?)</TAG>"

부동 소수점

r"[-+]?((\d*\.?\d+)|(\d\.))([eE][-+]?\d+)?"

파일 입출력

|

Python에서의 파일 입출력

일반적으로 파일 입출력(IO, Input/Output)은 다음과 같은 형태를 가집니다.

file = open(filepath, mode="r")
#...
file.close()

하지만, with 문을 사용할 경우 구문이 종료되면 파일을 자동으로 닫아주기 때문에 좀 더 효율적으로 사용할 수 있습니다.

with open(filepath, mode="r") as file:

파일에서 바이너리 데이터를 읽을 경우 다음과 같은 함수들을 사용할 수 있습니다.

  • file.read() : 모든 데이터를 문자열이나 바이너리 형태로 읽는다.
  • file.read(n) : n 개의 바이트를 읽는다.
  • file.readline() : 한 라인을 읽는다.
  • file.readlines() : 모든 라인을 문자열 리스트 형태로 읽는다.
  • file.write(line) : 한 라인을 기록한다.
  • file.writelines(lines) : 문자열 리스트를 기록한다.

Python vs 아니콘다(Anaconda) 차이점

|

Python vs 아나콘다

Python과 아나콘다(Anaconda)의 차이점은 다음과 같습니다.

Image

Python은 파이썬 공식 홈페이지에서 받을 수 있으며, pip 툴만을 포함하고 있습니다. 필요한 패키지나 라이브러리 등을 설치할 때 모두 수동으로 해줘야 합니다.

아나콘다는 Python 기본 패키지에 각종 수학/과학 라이브러리들을 같이 패키징해서 배포하는 버전입니다. 여기에서 다운로드 할 수 있습니다. 아나콘다에 포함된 툴들로는 대표적으로 panda, numpy, scipy, sklearn, matplotlib, Jupyter Notebook 등이 있습니다.


설치시 유의 사항

Python이나 아나콘다 중 하나만 설치하는 것을 추천합니다. 둘 다 설치할 경우 중복되는 파일들이 많으며 환경 변수 충돌 등의 문제가 발생할 수도 있습니다.


어떤 것을 선택할 것인가

아나콘다에 포함되어 있는 라이브러리들이 불필요한 경우에는 기본 Python만 설치해도 무관합니다. 하지만, 요즘 유행하는 인공지능이나 빅데이터 관련 개발을 할 경우에는 결국 아나콘다에 포함된 라이브러리들을 설치할 가능성이 높기 때문에 애초에 아나콘다를 설치하는 것이 더 유리합니다. 일일이 라이브러리들을 설치하다보면 의존성 문제 등이 발생할 수도 있기 때문입니다.


아나콘다 사용법

conda search python

설치되어 있는 Python의 버전 또는 라이브러리들의 버전을 확인할 수 있습니다.


conda create -n py36 python=3.6.1 anaconda

3.6.1 버전의 Python을 사용하는 py36이라는 환경을 생성합니다. 생성된 환경은 아래의 명령어를 이용해서 활성화/비활성화를 할 수 있습니다.

active py36
deactive py36


conda install python=3.6.1

위 명령어는 아나콘다의 Python 기본 버전을 설정하는 명령어입니다.