pytest doc의The writing and reporting of assertions in tests을 읽어보던 중에 with 구문과 context manager에 대한 내용이 있었는데 개념을 잘 몰라서 정리해봤다. 관련 글인Understanding Python’s “with” statement을 참고하여 작성했다.

with은 파이썬 2.5에서 도입된 기능으로 context manager에 의해서 실행되는__enter__()과__exit__()을 정의하여,with 구문 body의 앞부분과 뒷부분에 실행되는 코드를 대신할 수 있다.
with 구문을 이용하면 try/finally을 대신하여 더 간편하고 쉽게 사용할 수 있다.

아래의 예제 코드를 보자.

set things up
try: 
   do something 
finally:
   tear things down

set things up에는 file을 열거나 외부 리소스와 같은 것을 얻는 처리가 해당되고,tear things down에는 file을 닫거나 리소스를 제거, 해제 하는 처리가 해당된다. 이와 같은try-finally구조는 코드가 제대로 동작하지 않고 끝나더라도tear things down은 무조건 실행되는 것을 보장한다.

이런 코드는 많이 사용되는 것으로, 아래와 같이set things up과tear things down부분을__enter__()과__exit__()을 정의하여 사용한다면 재사용성이 높아지고 편리할 것이다.

class controlled_execution:
    def __enter__(self):
        set things up
        return thing
    def __exit__(self, type, value, traceback):
        tear things down

with controlled_execution() as thing:
     some code using thing

with구문이 실행되면, context manager에 의해서__enter__이 실행되고 여기서 반환하는 값이as의 thing로 지정된다. 그 후에some code using thing에 해당하는 body code를 실행하고,코드에 무슨 일이 있다 하더라도마지막에__exit__은 호출이 보장된다.

python의file객체는__enter__와__exit__함수가 구현되어있다. 전자는 file object 객체 자신을 리턴하고, 후자는 file을 close 한다.

>>> f = open("x.txt")
>>> f
<open file 'x.txt', mode 'r' at 0x00AE82F0>
>>> f.__enter__()
<open file 'x.txt', mode 'r' at 0x00AE82F0>
>>> f.read(1)
'X'
>>> f.__exit__(None, None, None)
>>> f.read(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file

따라서 file을 열고, 사용한 후에 닫는 것을 보장하도록 하는 것을 아래처럼 매우 간단하게 사용할 수 있다.

with open("x.txt") as f:
    data = f.read()
    do something

참고자료

'C Lang > Python Program Diary' 카테고리의 다른 글

Python requests 모듈 간단 정리  (0) 2019.06.14
파이썬 에러처리하기  (0) 2019.06.14
문자열 format하기  (0) 2019.06.10
쓰레드 (Thread)란  (0) 2019.06.07
Process와 Thread의 차이  (0) 2019.06.07

'C Lang > Python Program Diary' 카테고리의 다른 글

파이썬 에러처리하기  (0) 2019.06.14
__enter__, __exit__, with 구문  (0) 2019.06.11
쓰레드 (Thread)란  (0) 2019.06.07
Process와 Thread의 차이  (0) 2019.06.07
if __name__ == "__main__": 의미  (0) 2019.06.06

쓰레드 (Thread)

파이썬 프로그램은 기본적으로 하나의 쓰레드(Single Thread)에서 실행된다. 즉, 하나의 메인 쓰레드가 파이썬 코드를 순차적으로 실행한다. 코드를 병렬로 실행하기 위해서는 별도의 쓰레드(Subthread)를 생성해야 하는데, 파이썬에서 쓰레드를 생성하기 위해서는 threading 모듈 (High 레벨) 혹은 thread 모듈 (Low 레벨)을 사용할 수 있다. 일반적으로 쓰레드 처리를 위해서는 thread 모듈 위에서 구현된 threading 모듈을 사용하고 있으며, thread 모듈은(deprecate 되어)거의 사용하고 있지 않다.

파이썬(오리지날 파이썬 구현인 CPython)은 전역 인터프리터 락킹(Global Interpreter Lock) 때문에 특정 시점에 하나의 파이썬 코드만을 실행하게 되는데, 이 때문에 파이썬은 실제 다중 CPU 환경에서 동시에 여러 파이썬 코드를 병렬로 실행할 수 없으며 인터리빙(Interleaving) 방식으로 코드를 분할하여 실행한다. 다중 CPU 에서 병렬 실행을 위해서는 다중 프로세스를 이용하는 multiprocessing 모듈을 사용한다.

threading 모듈

파이썬에서 쓰레드를 실행하기 위해서는, threading 모듈의 threading.Thread() 함수를 호출하여 Thread 객체를 얻은 후 Thread 객체의 start() 메서드를 호출하면 된다. 서브쓰레드는 함수 혹은 메서드를 실행하는데, 일반적인 구현방식으로 (1) 쓰레드가 실행할 함수 혹은 메서드를 작성하거나 또는 (2) threading.Thread 로부터 파생된 파생클래스를 작성하여 사용하는 방식 등이 있다.

먼저 첫번째 함수 및 메서드 실행 방식은 쓰레드가 실행할 함수 (혹은 메서드)를 작성하고 그 함수명을 hreading.Thread() 함수의 target 아큐먼트에 지정하면 된다. 예를 들어, 아래 예제에서 sum 이라는 함수를 쓰레드가 실행하도록 threading.Thread() 함수의 파라미터로 target=sum 을 지정하였다. 여기서 한가지 주의할 점은 target=sum() 처럼 지정하면, 이는 sum() 함수를 실행하여 리턴한 결과를 target에 지정하는 것이므로 잘못된 결과를 초래할 수 있다. 만약 쓰레드가 실행하는 함수(혹은 메서드)에 입력 파라미터를 전달해야 한다면, args (혹은 키워드 아규먼트인 경우 kwargs) 에 필요한 파라미터를 지정하면 된다. args는 튜플로 파라미터를 전달하고, kwargs는 dict로 전달한다. 아래 예제에서 sum() 함수는 두 개의 파라미터를 받아들이기 때문에 "args=(1, 100000)" 와 같이 입력파라미터를 지정하였다.

import threading
def sum(low, high):
    total = 0
    for i in range(low, high):
        total += i
    print("Subthread", total)
t = threading.Thread(target=sum, args=(1, 100000))
t.start()
print("Main Thread")

(실행결과)

$ python thrd.py 
Main Thread 
Subthread 
4999950000

threading.Thread 로부터 파생클래스를 만드는 방식은 Thread 클래스를 파생하여 쓰레드가 실행할 run() 메서드를 재정의해서 사용하는 방식이다. Thread 클래스에서 run() 메서드는 쓰레드가 실제 실행하는 메서드이며, start() 메서드는 내부적으로 이 run() 메서드를 호출한다. 예를 들어, 아래 예제(A)는 getHtml() 라는 함수를 사용한 방식인데 이를 예제(B)와 같이 파생클래스를 사용하는 방식으로 바꿔 쓸 수 있다. 예제(B)에서 t.start()는 HtmlGetter 클래스에서 재정의된 run() 메서드를 호출하게 된다.

\# 예제(A)
import threading, requests, time
def getHtml(url):
    resp = requests.get(url)
    time.sleep(1)
    print(url, len(resp.text), ' chars')
t1 = threading.Thread(target=getHtml, args=('[http://google.com](http://google.com)',))
t1.start()
print("### End ###")
\# 예제(B)
import threading, requests, time

class HtmlGetter (threading.Thread):
    def \_\_init\_\_(self, url):
        threading.Thread.\_\_init\_\_(self)
        self.url = url
    def run(self):
        resp = requests.get(self.url)
        time.sleep(1)
        print(self.url, len(resp.text), ' chars')
t = HtmlGetter('[http://google.com](http://google.com)')
t.start()
print("### End ###")

데몬 쓰레드

Thread 클래스에서 daemon 속성은 서브쓰레드가 데몬 쓰레드인지 아닌지를 지정하는 것인데, 데몬 쓰레드란 백그라운드에서 실행되는 쓰레드로 메인 쓰레드가 종료되면 즉시 종료되는 쓰레드이다. 반면 데몬 쓰레드가 아니면 해당 서브쓰레드는 메인 쓰레드가 종료할 지라도 자신의 작업이 끝날 때까지 계속 실행된다.

아래 예제는 데몬 쓰레드를 예시하기 위한 것으로 Thread 객체의 daemon 속성을 True로 설정한 후 start() 하면, 해당 서브쓰레드는 데몬 쓰레드가 되고 아래와 같이 메인 쓰레드가 곧바로 종료되면 getHtml 메서드를 마저 실행하지 못하고 바로 데몬 쓰레드를 종료하게 된다. daemon 속성은 디폴트로 False 이므로 별도로 지정하지 않으면 메인 쓰레드가 종료되어도 서브쓰레드는 끝까지 작업을 수행한다.

import threading, requests, time
def getHtml(url):
    resp = requests.get(url)
    time.sleep(1)
    print(url, len(resp.text), ' chars')
\# 데몬 쓰레드
t1 = threading.Thread(target=getHtml, args=('[http://google.com](http://google.com)',))
t1.daemon = True 
t1.start()
print("### End ###")

(실행결과)

$ python thrd2.py
### End ###

'C Lang > Python Program Diary' 카테고리의 다른 글

__enter__, __exit__, with 구문  (0) 2019.06.11
문자열 format하기  (0) 2019.06.10
Process와 Thread의 차이  (0) 2019.06.07
if __name__ == "__main__": 의미  (0) 2019.06.06
psycopg2의 빈번한 커멘드 정리  (0) 2019.05.22

Process와 Thread의 차이

(https://shoark7.github.io/programming/knowledge/difference-between-process-and-thread.html)

요약 : 프로세스와 스레드의 근본적인 차이는 프로세스는 운영체제로부터 독립된 시간, 공간 자원을 할당 받아 실행된다는 점이고, 스레드는 한 프로세스 내에서 많은 자원을 공유하면서 병렬적으로(Concurrently) 실행된다는 것이다. 다른 차이는 모두 이 근본적인 차이에서 비롯된다.

1. 들어가며


오늘 포스트는 Process(이하 “프로세스”), Thread(이하 “스레드”)의 차이에 대해 다룬다.개발 면접 시 단골 질문이기에 관련된 수많은 포스트들이 있는데 내가 필요할 때 다시 돌아와서 볼 수 있는 나만의 내용을 정리하고 싶어 작성한다.

포스트의 순서는

  1. 프로세스에 대해 먼저 알아보고,
  2. 스레드에 대해 알아본다.
  3. 다음은 이 둘의 차이를 파악한다.

2. 프로세스


프로세스를 이해하기에는 먼저 프로그램(Program)과 비교하면 편하다.

“프로그램은 실행가능한 명령어(instruction)의 집합”이다. 프로그램은 보통 디스크에 저장되어 컴파일된 바이너리 이미지 형태일 수도 있고, 파이썬 스크립트 같이 해석되는(Interpret) 고급어 형태일 수도 있다. 프로그램의 정의는 꽤나 포용적이기 때문에 당신의 컴퓨터에 설치된 포토샵 파일도 프로그램이고 또 내가 짤 수 있는 파이썬 구구단 출력 스크립트 파일도 프로그램이다. 중요한 건디스크에 저장된 실행 가능한 명령어의 집합인지의 여부이다.후자가 면접자들에게 깊은 인상을 남길 수 있을지는 모르겠지만 말이다.

 

프로세스는 여기서 출발한다. 프로그램은 애시당초 누군가에게 쓰여지기를 목적으로 개발되었고 실제로 누군가에 의해 실행된다.“프로세스는 메모리에 적재(load)되어 실행되고 있는 프로그램”을 말한다.정적인 프로그램과 달리 프로세스는 실제 실행 중인 프로그램을 일컫기 때문에 동적이라고 표현하기도 한다.

프로세스를 “프로그램의 인스턴스”라고 표현하기도 하는데 실제 객체 지향의 클래스와 인스턴스와 비교/대조하면 재밌다. 프로그램과 프로세스의 관계와 클래스와 인스턴스의 관계의 공통점은 한 클래스가 여러 인스턴스를 생성할 수 있는 것과 같이한 프로그램에서 실행되는 여러 프로세스가 동시에 존재할 수 있다.윈도우즈 유저라면 메모장을 여러 개 실행시킴으로써 이를 증명할 수 있다. 차이는 프로그램은 클래스처럼 다른 프로그램을 상속하지는 않는다. 라이브러리나 모듈이라는 이름으로 사용할 수는 있지만.

 

프로세스는 커널에 의해 직접 관리되는데 커널 메모리 안에는 각 프로세스마다 관리하고 있는 프로세스에 대한 데이터들이 있다. 이 정보는 Process Control Block(이하 “PCB”)이라고 하는 자료구조 안에 있는데 커널 스케쥴러가 프로세스를 제어하는 데 필요한 정보들이 담겨 있다.PCB는 다음과 같은 정보와 자원을 포함한다.

  • 프로그램과 관련된 실행가능한 기계어 이미지
  • 운영체제와 관련해 할당된 자원의 식별자(유닉스의 File Descriptor, 윈도우즈의 Handle 등)
  • 프로세스의 소유자 등 프로세스와 관련된 Permission 정보
  • Context라고 일컬어지는 프로세스 상태. 물리적 메모리 주소나 CPU 내 레지스터의 내용, 실행 중인 명령어를 지정하는 Program Counter 등을 포함한다.
  • 실행되는 프로세스에 대한 메모리 주소

이때커널 메모리 안에서 관리되는 PCB 정보가 아닌 유저가 사용하는 메모리 공간 상의 프로세스 정보는 다음 4가지 분류로 다시 나뉜다.

  • Code(text): 프로그램의 실제 코드를 저장
  • Data: 프로세스가 실행될 때 정의된 전역 변수, Static 변수들을 저장
  • Heap: 프로세스 런타임 중 동적으로 할당되는 변수들을 저장(함수 내에서 할당되는 변수 등)
  • Stack: 함수에서 다른 함수를 실행하는 등의 서브루틴들의 정보를 저장(재귀와 스택이 관련 있는 이유)

위 그림처럼 메모리 상의 프로세스들은 4가지의 정보 집합으로 구성되며 이 네 가지 정보 분류는 이후 프로세스와 스레드의 차이를 다룰 때 다시 나온다.

운영체제는 각각의 프로세스는 독립적으로 관리하기 때문에 서로 다른 프로세스가 겹칠 일이 없고, 또 사용 자원 영역 등이 겹치는 일이 발생해서도 안 된다.가령 한 프로세스가 다른 프로세스의 정보 한 부분을 변경하면 그 프로세스에 치명적인 오류가 날지도 모르는 일이기 때문이다.한 가지 예외로 같은 프로그램의 프로세스들은 Code 영역은 공유한다.내용이 동일한 프로그램의 코드를 여러 개 복사해서 프로세스마다 가지고 있는 것 보다는 메모리 상의 코드 공간을 주소로 참조하는 것이 상식적으로 낫다.

 

대부분의 운영체제는 독립적인 각 프로세스가 다른 프로세스의 정보를 변경하는 것을 극도로 주의하고 있으며 필요할 경우최소한의 인터페이스를 제공해 소통할 수 있도록 하고 있다. 이런 프로세스간 소통을 Inter Process Communication(IPC)이라고 한다.프로세스간 통신의 예로는 다음과 같은 유닉스 파이프라이닝이 있을 수 있겠다.

 

$ cat many-names | sort | uniq

참고로 프로세스간 통신은 꼭 같은 컴퓨터에서의 프로세스를 가정할 이유는 없다. 다른 컴퓨터에 위치한 두 프로세스가 통신할 수도 있는데 이때 운영 시스템 등이 다를 수 있기 때문에 통신을 위해 그 유명한 ‘Protocol’이 필요해진다.

2.1.잠깐, Multitasking과 Context switch

이후 내용을 진행하기에 앞서 운영체제와 관련해서 Multitasking과 Context switch에 대해 정리할 필요가 있다. 이들은 운영체제에서 프로세스를 운영하는 것과 관련이 있다.

 

나는 지금 음악을 들으면서 포스팅을 하고 있다. 경우에 따라서는 검색도 하고 영어사전도 찾아가면서 여러 작업을 동시에 진행하는데 이런 작업들, 음악 재생, 파일의 내용 변경과 디스크 저장, HTTP 통신 등은 다 프로세스들에 의해 진행되고 있다.이렇게 한 컴퓨터에서 여러 가지 작업을 동시에 하는 것을 Multitasking이라고 하며 현대적인 운영체제는 모두 Multitasking을 지원한다.

 

근데 참 신기하다. 컴퓨터가 대단하다고는 하지만 어떻게 수많은 작업을 동시에 하고 있는 것일까? 실은 프로세스들은 동시에 실행되고 있지 않다.Time sharing이라는 기법 안에서 여러 프로세스들은 “동시”라고 느껴질만큼 매우 짧은 순간 동안 작업하고(CPU를 점유하고) 다른 프로세스에 CPU 자원을 양보하는 것이다.이렇게 작업이 전환되는 속도가 매우 짧아 인간은 이를 프로세스들이 동시에 진행되고 있다고 믿게 된다.(원래 인간은 사실보다는 사실이라고 믿는 것을 더 좋아한다.)

 

이때 생기는 문제가 있다. 어떤 프로세스에서 점유하던 CPU 자원을 멈추고 다른 프로세스에 양보했다고 치자. 다시금 자기 차례가 되었을 때 마지막으로 어떤 작업 중이었는지 어떻게 기억해야 할까? 이는 중요한 이슈다.

앞서 작업 중인 프로세스에 대한 정보를 Context라고 했다.작업을 중단했을 때의 Context 정보는 프로세스가 전환될 때 PCB에 저장한다. 이후 자신의 차례가 다시 왔을 때 PCB에 저장된 상태에서 작업을 재개하면 된다.

이렇게프로세스가 전환되면서 Context를 전환하는 것을 Context Switch라고 한다.이때 작업 중이던 Context를 저장하고 새로운 Context를 로드하면서 CPU 레지스터 상태 변환, 스택 포인터 추적, 명령어를 추적하는 Program Counter 등에 대한 작업을 처리하기 때문에 오버헤드가 발생한다. 하지만 그럼에도 멀티태스킹에 대한 수요가 확실하기 때문에 많은 운영체제가 Context Switch를 최적화하는 데 집중하고 있다.

3. 스레드


내 생각에 스레드는 이 단 하나의 문장으로 정의하면 된다.

“프로세스 내에서 실행되는 흐름”

파이썬 스크립트 파일을 보자. 그 파일을 실행시키면 한 줄 한 줄 파일이 실행될 것을 우리는 안다.그렇게 실행되는 일련의 흐름이 곧 스레드다.

 

일반적으로 하나의 프로세스는 하나의 스레드로 시작되며 이를 메인 스레드라고 한다. 스레드를 추가로 생성하지 않는 한 모든 프로그램은 메인 스레드에서 실행된다.

MS-Dos 등 싱글스레딩 운영체제와 달리 현대적인 운영체제에서는 하나의 프로세스 내에 여러 스레드가 동시에 존재할 수 있는(Concurrent) 멀티스레딩(Multi-threading)을 지원한다.이 말은 곧 실행 중인 프로세스 내에 여러 흐름이 존재할 수 있다는 것이 되는데 애초에 이런 일이 왜 필요할까?

 

생각을 해보자. 우리가 일반적으로 만드는 프로그램은 한 줄 한 줄 순서대로 실행한다. 문제를 구성하는 부분문제들이 있고 각 부분문제들에 대한 해답이 순차적으로 연결된 구조를 가질 것이다. 가령 우리가 json 파일을 DB처럼 쓰고 있는데 내용을 업데이트해야 한다고 치자. 우리의 작업은 1. 파일을 읽어와 2. 내용을 변경한 뒤 3. 디스크에 저장하는 세 가지의 부분작업이 순차적으로 연결되어 있는 것이다. 각 작업간 의존관계가 있기 때문에 어쩔 수 없기는 하지만,이런 순차적인 방식의 단점은 한 작업이 오래 걸리면 전체 프로그램이 지연되는 병목현상이 생길 수 있다는 점이다.

 

하지만세상에는 이렇게 모든 작업이 선형적으로 연결되어 실행되지 않아도 되는 경우도 있다. 한 프로세스 내에서 서로 순서상 의존하지 않는 작업이 다른 작업의 종료를 기다릴 이유가 없는 것이다. 이때 실행되는 흐름, 스레드를 여러 개 두면 병목현상에 걸리지 않고 전체 작업시간을 줄일 수 있다.

 

운영체제적으로는 한 프로세스 안의 스레드들은 스택 공간을 제외한 프로세스의 나머지 공간과 시스템 자원을 공유하는데이는 스레드가 관련된 여러 장점과 단점을 갖게 한다.

스레드를 사용할 때의 장점은 다음과 같다.

  • 프로세스 간 통신에 비해 스레드 간 통신이 훨씬 간단하다.
    • 서로 공유하는 변수를 변경하기만 하면 되기 때문이다. 반면 프로세스 간 통신은 그 위험성으로 까다롭게 관리된다.
  • 시스템의 자원 소모가 줄어든다.
    • 기존 프로세스의 자원을 다른 스레드와 공유하기 때문에 자원을 새로 할당하지 않아도 된다.
  • 전체 응답 시간이 단축된다.
    • 시간도 자원이기에 오버헤드가 줄어들어 전체 응답이 짧아진다. 또 병목이 걸리는 작업과 다른 작업을 구분할 수 있어 전체 실행시간을 줄일 수 있다.

위와 같은 장점은 왜 웹 서버가 각각의 HTTP 통신을 멀티프로세스가 아닌 멀티스레드로 구현하는지를 설명한다. 각 통신을 고유한 자원을 할당해야 하고 서로 간 통신도 까다로운 프로세스로 구현하고 싶지는 않을테니까 말이다.

하지만 스레드에는 단점도 있다.

  • 여러 스레드를 이용하는 프로그램을 작성하는 경우에는 설계를 신경써야 한다. 미묘한 시간 차나 잘못된 변수를 공유함으로써 문제가 발생할 수 있다.(더 큰 자유에는 더 큰 책임이 따른다)
  • 디버깅이 어렵다.

3. 프로세스와 스레드의 차이


 

 

프로세스와 스레드의 근본적인 차이는프로세스는 운영체제로부터 독립된 시간, 공간 자원을 할당 받아 실행된다는 점이고, 스레드는 한 프로세스 내에서 많은 자원을 공유하면서 병렬적으로(Concurrently) 실행된다는 것이다.다른 차이는 모두 이 근본적인 차이에서 비롯된다.

 

이로부터 파생되는 여러 차이는 다음과 같다.

먼저프로세스는 보다 독립적이다.서로 구분되는 자원을 할당 받아 정말 필요한 경우가 아니면 다른 프로세스에 영향을 미치지 않고 실행된다. 반면스레드는 프로세스의 하위 집합으로 여러 스레드가 같은 프로세스 자원을 공유하기 때문에 독립적이지 않다.같은 의미로 프로세스는 보유한 자원에 대한 별개의 주소 공간을 갖지만 스레드는 이 주소 공간을 공유한다.

 

프로세스간 통신은 스레드간 통신보다 어렵다.프로세스는 오직 시스템이 제공하는 IPC 메커니즘을 통해서만 통신할 수 있고 시스템에 의해 관리되기 때문에 상대적으로 안전하다. 반면에 스레드는 단순히 공유 변수 수정만으로도 스레드간 통신을 구현할 수 있어 통신이 매우 용이하지만, 안전한 프로그램을 만들기 위해서는 신중해야 한다.

 

Context Switch에 있어서도 프로세스보다 스레드가 “일반적으로” 더 빠르고 자원소모가 적다.프로세스는 Switch될 때의 Context를 PCB 등에 저장하는 등 오버헤드가 발생하는데 스레드는 그런 부하가 적다. 근데 이 부분은 조금 조심해야 한다. 압도적으로 스레드 Switching이 더 저렴하다는 의견이 있는 반면 운영체제나 배포판에 따라, 프로세스의 환경에 따라 거의 차이가 없을 수도 있다는 의견 등이 분분하다.이 둘의 차이를 표로 한 번 정리해보자.

 

차이 프로세스 스레드

자원 할당 여부 실행 시마다 새로운 자원을 할당 자신을 실행한 프로세스의 자원을 공유
자원 공유 여부 일반적으로 자원을 공유하지 않는다. 같은 프로그램의 프로세스일 경우 코드를 공유하기는 한다. 같은 프로세스 내 스레드들은 스택을 제외한 나머지 세 영역을 공유한다.
독립성 여부 일반적으로 독립적 일반적으로 프로세스의 하위 집합
주소 소유 여부 별개의 주소 공간을 갖는다 주소 공간을 공유한다.
통신 여부 오직 시스템이 제공하는 IPC 방법으로만 통신 공유 변수 수정 등 자유롭게 다른 스레드와 소통
Context Switch 일반적으로 프로세스보다 스레드의 Context Switching이 더 빠를 수 있다. 하지만 상황에 따라 그렇지 않을 수도 있다.

4. 자료 출처


TwitterFacebook

'C Lang > Python Program Diary' 카테고리의 다른 글

문자열 format하기  (0) 2019.06.10
쓰레드 (Thread)란  (0) 2019.06.07
if __name__ == "__main__": 의미  (0) 2019.06.06
psycopg2의 빈번한 커멘드 정리  (0) 2019.05.22
centOS7에서 conda 인스톨하기  (0) 2019.03.17

불러오는 중입니다...

파이썬 프로그래밍을 보다보면 아래와 같은 문장을 만나곤 한다. 정확하게 어떤 의미인지 알고 싶어 여기저기 검색한 내용을 정리했다.

if name == "main"

이 문장을 이해하기 위해서는 파이썬의 namespace 라는 개념을 이해해야 한다. namespace 를 얘기하기 이전에 파이썬에서 name(변수명)이 의미하는 것을 생각해보자.

아래와 같이 파이썬에서는 name 에 값을 줄 수 있다. 그리고 값 뿐 아니라 function 과 같은 형태도 name 을 줄 수 있다. 또한 동일한 name을 재사용할 수 있다.

i = 12
s = "Hello World"
l = [1, 2, 3]

def foo():
print("This is a function")
f = foo

var = 12
var = "Hello World"
var = [1, 2, 3]

파이썬에서 names는 파이썬 객체 시스템과 함께 간다고 생각하면 된다. 즉 integer, string, list 및 function 도 모두 파이썬에서는 객체형태로 표현되고, name은 그 객체에 접근하기 위해 사용한다.

namespace 와 module

namespace 는 names 를 담을 수 있는 공간이라고 생각하면 된다. 파이썬에서 namespace 를 이해하기 위해서 파이썬 모듈에 대한 약간의 이해가 필요하다. 파이썬에서 module 은 파이썬 코드를 담고 있는 파일이다. 해당 파일에는 파이썬 클래스, 함수 또는 단순하게 names 의 리스트가 들어있을 수 있다.

각 모듈은 자신만의 유일한 namespace 를 갖는다.(모듈의 namespace 이름은 보통 모듈의 파일이름과 같다.) 그래서 동일한 모듈내에서 동일한 이름을 가지는 클래스나 함수를 정의할 수 없다. 또한 모듈은 각각 완벽하게 독립적(isolated)이기 때문에(namespace 가 다르기 때문에), 두 모듈은 동일한 이름을 갖는 클래스나 함수를 정의할 수 있다.

import 와 namespace

import 명령을 가지고 namespace 에 대해서 조금 더 알아보도록 하자. module 을 import 하는 방법은 여러가지가 있다. 방법에 따라 namespace 가 달라 질 수가 있다.

  1. import
    모듈을 import 하는 가장 간단한 방법이고, 일반적으로 추천되는 방법이다. 이렇게 import 를 하게 되면 module 의 name 을 prefix 로 사용함으로써 모듈의 namespace 에 접근할 수 있다.
    아래 예제에서 sys 는 모듈 이름이고, path 는 sys 모듈의 namespace 에 담겨있는 name 이다. 따라서 path 에 접근을 하기 위해서는 모듈 이름인 sys 를 prefix 로 붙여서 sys 모듈의 namespace 에 접근한 후에 사용해야 한다.
import sys
sys.path

['', 'C:\\Python34\\Lib\\idlelib', 'C:\\Windows\\system32\\python34.zip', 'C:\\Python34\\DLLs', 'C:\\Python34\\lib', 'C:\\Python34', 'C:\\Python34\\lib\\site-packages']
  1. from import <name,>
    모듈의 namespace 에서 import 에서 지정된 name 들을 직접 가져오도록 한다. 이렇게 하게 되면 import 이후에 지정한 name 들은 module 의 name을 prefix 로 지정하지 않고도 접근이 가능하다. 하지만, 이 경우에 module 에서 import 된 이름과 main script 에서 지정된 이름이 동일한 경우, 나중에 정의되는 이름으로 대체되어서 이전 것에 접근이 불가능하게 된다.

    단지 몇개의 name 만 필요하다고 명확하게 알고 있는 경우에 사용하는 것이 유용하다.

from sys import path
path

['', 'C:\\Python34\\Lib\\idlelib', 'C:\\Windows\\system32\\python34.zip', 'C:\\Python34\\DLLs', 'C:\\Python34\\lib', 'C:\\Python34', 'C:\\Python34\\lib\\site-packages']
  1. from import *

    2 와 동일하지만, module 에 있는 모든 name 을 직접 현재 namespace 로 가져오게 된다. 이렇게 되면 namespace 가 섞이게 되어서 일반적으로 사용을 권장하지 않는다. 차라리 첫번째 타입(1번)의 import 를 사용하는 것이 좋다.

__main__ namespace

import 의 경우에 namespace 가 처리되는 것을 알아보았는데, import 가 아니고 파이썬 인터프리터가 최초로 파일을 읽어서 실행하는 경우를 살펴보자. 파이썬 인터프리터는 소스파일을 읽고, 그 안의 모든 코드를 실행하게 되는데, 코드를 실행하기 전에 특정한 변수값을 정의한다. 그중 하나가 name 이라는 변수를 main 으로 세팅을 한다.

➡ 소스코드를 실행시키는 방법은 두가지가 있는데 하나는 1.직접실행시키는 방법이고 두번째는 2. 파이썬 스크립트 내부에서 import로 파일을 실행시키는 방법이다. 직접실행시키는 경우 namespace가 모듈명이 아닌 main 으로 실행되지만, import되는 경우는 모듈명으로 실행이 되게 된다.

즉 python script.py 와 같이 직접 쉘에서 실행하는 경우에는 파이썬 인터프리터가 해당 script.py 모듈을 script 라는 namespace 가 아닌main 이라는 namespace 로 간주하여 다루게 된다.

따라서 처음에 궁금했던 아래 문장은 '만일 이 파일이 인터프리터에 의해서 실행되는 경우라면' 이라는 의미를 갖는다

if __name__ == "__main__"

즉 본인이 구현한 코드가 다른 파이썬 코드에 의해서 모듈로 import 될 경우도 있을 수 있고, 파이썬 인터프리터에 의해서 직접 실행될 경우도 있을 수 있는데, 위 코드는 인터프리터에 의해서 직접 실행될 경우에만 실행하도록 하고 싶은 코드 블럭이 있는 경우에 사용한다.

아래 예제 코드와 결과를 보면 이해하기 쉽다.(참고: http://ibiblio.org/g2swap/byteofpython/read/module-name.html)

#!/usr/bin/python
# Filename: using_name.py

if __name__ == '__main__':
    print 'This program is being run by itself'
else:
    print 'I am being imported from another module'
$ python using_name.py
This program is being run by itself

$ python
>>> import using_name
I am being imported from another module
>>>

출처: https://pinocc.tistory.com/175 [땅뚱 창고]

'C Lang > Python Program Diary' 카테고리의 다른 글

문자열 format하기  (0) 2019.06.10
쓰레드 (Thread)란  (0) 2019.06.07
Process와 Thread의 차이  (0) 2019.06.07
psycopg2의 빈번한 커멘드 정리  (0) 2019.05.22
centOS7에서 conda 인스톨하기  (0) 2019.03.17

psycopg2 でよくやる操作まとめ

original article

https://qiita.com/hoto17296/items/0ca1569d6fa54c7c4732

入る前

Python から pyscopg2 を使って PostgreSQL サーバにアクセスするときによくやる操作をまとめておく。
他にも思いついたら随時追記していく。

DB-API について

psycopg2 は PEP 249 -- Python Database API Specification v2.0 で定められているインタフェースを満たす API を提供している。

import psycopg2

print(psycopg2.apilevel)  #=> '2.0'

これにより、MySQL などの他のデータソースと同じようにコネクションやカーソルを操作してデータベースを触ることができる。

PostgreSQL サーバに接続する

psycopg2 は接続情報を文字列で指定するだけでよしなにパースして接続してくれる。

import os
import psycopg2

def get_connection():
    dsn = os.environ.get('DATABASE_URL')
    return psycopg2.connect(dsn)

環境変数 DATABASE_URL には postgresql://{username}:{password}@{hostname}:{port}/{database} というフォーマットでデータベースの接続情報を指定する。

以下、この get_connection 関数を使ってコネクションオブジェクトを取得するものとする。

クエリを実行する

コネクションオブジェクトからカーソルオブジェクトを取得して、クエリを実行する。

conn = get_connection()
cur = conn.cursor()
cur.execute('SELECT * FROM users')
cur.close()
conn.close()

with 文を使うと以下のように書ける。

with get_connection() as conn:
    with conn.cursor() as cur:
        cur.execute('SELECT * FROM users')

例外が発生した場合などに close し忘れる等のミスを防げるので、 with 文を利用した方がいい。

クエリにパラメータを埋め込む

cur.execute の第二引数にタプル (またはリスト) を渡すことでクエリにパラメータを安全に埋め込むことができる。

name = "' OR 1=1 --"  # 悪意のあるパラメータ
cur.execute('SELECT * FROM users WHERE name = %s', (name,))

print(cur.query)  #=> "SELECT * FROM users WHERE name = ''' OR 1=1 --'"

クエリの実行結果を取得する

ひとつだけ取得する

cur.execute('SELECT COUNT(1) FROM users')
(count,) = cur.fetchone()

まとめて取得する

cur.execute('SELECT * FROM users')
rows = cur.fetchall()

ひとつずつ取得する

cur.execute('SELECT * FROM users')
for row in cur:
    print(row)

実行結果のカラム名を取得する

カーソルオブジェクトの description に各カラムの情報が含まれている。

cur.execute('SELECT * FROM users')
colnames = [col.name for col in cur.description]

実行結果を辞書形式で取得する

カーソルオブジェクト取得時の cursor_factory パラメータにpsycopg2.extras.DictCursor を指定すると、実行結果がディクショナリとして取得できる。

from psycopg2.extras import DictCursor

with get_connection() as conn:
    with conn.cursor(cursor_factory=DictCursor) as cur:
        cur.execute('SELECT COUNT(1) AS count FROM users')
        row = cur.fetchone()
        print(row)  #=> { "count": 123 }

レコードを追加する

with get_connection() as conn:
    with conn.cursor() as cur:
        cur.execute('INSERT INTO users (name) VALUES (%s)', ('foo',))
    conn.commit()

psycopg2 ではデフォルトでトランザクションが有効になっているので commit を行わないと反映されない。

コネクションオブジェクトの生成に with 文を利用していると、ブロック内で例外が発生した場合に自動で conn.rollback() が呼ばれるため、明示的にロールバックを実行する必要はない。

トランザクションを無効にする場合は autocommit = True を設定する。

with get_connection() as conn:
    conn.autocommit = True
    with conn.cursor() as cur:
        cur.execute('INSERT INTO users (name) VALUES (%s)', ('foo',))

クエリのタイムアウトを設定する

psycopg2 にはクエリのタイムアウトを設定するようなパラメータはなさそうなので、自分で statement_timeout を設定する。

from psycopg2.extensions import QueryCanceledError

try:
    with get_connection() as conn:
        with conn.cursor() as cur:
            cur.execute('SET statement_timeout TO 100')  # 0.1秒でタイムアウトさせる
            cur.execute('SELECT pg_sleep(1)')  # 実行に1秒かかるクエリの例
except QueryCanceledError as err:
    print(err)  #=> "canceling statement due to statement timeout"

SET で設定した値はそのセッション内でのみ有効なので、他のクエリに影響することはない。同じセッション内で別のクエリも実行する場合は RESET statement_timeout すると値をリセットできる。

サーバサイドカーソルを利用する

通常の cur.execute() では、クエリの実行結果をすべてメモリに載せてからカーソルオブジェクトが参照していく。そのため、メモリに乗り切らないほど膨大なレコードが返ってくるクエリを実行することができない。

このような場合はサーバサイドカーソルを利用する。
サーバサイドカーソルを利用するとデータベース側で DECLARE CURSOR されるため、実行結果を少しずつ取得して処理することができるようになる。

詳しくは: PythonとDB: DBIのcursorを理解する - Qiita

psycopg2 では、名前付きカーソルを作成するとサーバサイドカーソルになる。

with conn.cursor('query1') as cur:

一度の fetch で取得する行数は cur.itersize で定められていて、デフォルトでは 2000 行ずつ取得するようになっている。特に問題がなければこのままでいいが、変更することもできる。

cur.itersize = 10000

サーバサイドカーソルを利用する際の注意点

サーバサイドカーソル利用時は、execute した時点ではカーソルが作成されるだけでまだクエリは実行されていない。クエリが実行されるのは最初に fetch したときなので、それ以降でないと cur.descriptioncur.rownumber を利用することができない。

with conn.cursor('query1') as cur:
    cur.execute('SELECT * FROM users')
    print(cur.description)  # None
    cur.fetchone()
    print(cur.description)  # カラム情報が入っている

execute の直後にカラム情報を取得する処理を書いている場合は注意が必要。

'C Lang > Python Program Diary' 카테고리의 다른 글

문자열 format하기  (0) 2019.06.10
쓰레드 (Thread)란  (0) 2019.06.07
Process와 Thread의 차이  (0) 2019.06.07
if __name__ == "__main__": 의미  (0) 2019.06.06
centOS7에서 conda 인스톨하기  (0) 2019.03.17




CentOS7 minimal install に Anaconda をインストール

インストールに必要なライブラリを yum で インストール

sudo yum install git bzip2 -y

参考:lbzip2: exec 不能: そのようなファイルやディレクトリはありません

Anaconda のインストール(2018/12/11)

下はユーザー権限で行う。
git clone https://github.com/yyuu/pyenv.git ~/.pyenv
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init -)"' >> ~/.bashrc
source ~/.bashrc
pyenv install anaconda3-5.3.1
pyenv rehash
pyenv global anaconda3-5.3.1
echo 'export PATH="$PYENV_ROOT/versions/anaconda3-5.3.1/bin/:$PATH"' >> ~/.basic
source ~/.bashrc
conda update conda

参考:CentOS7にAnacondaの環境構築



'C Lang > Python Program Diary' 카테고리의 다른 글

문자열 format하기  (0) 2019.06.10
쓰레드 (Thread)란  (0) 2019.06.07
Process와 Thread의 차이  (0) 2019.06.07
if __name__ == "__main__": 의미  (0) 2019.06.06
psycopg2의 빈번한 커멘드 정리  (0) 2019.05.22

+ Recent posts