2019-10-28

파이썬 멀티프로세싱의 .join() 메서드는 무슨 일을 하는걸까?

0. 멀티 프로세싱을 진행할 필요성이 있어 파이썬 예제 코드를 찾아보았고

multiprocessing을 사용하는 코드들은 대개 아래와 같은 구조를 갖고 있음을 발견했다.

from multiprocessing import Process

procs = []

proc = Process(target=sample_func, args=(number,))
procs.append(proc)
proc.start()
proc.join()

근데 마지막에 .join()은 역할이 뭘까?



1. 일단 제일 먼저 하는건 역시나 문서 검색(한글, 영문)

.join([timeout]) 의 아규먼트인 timeout의 값이 None(기본값)인 경우

메서드는 join() 메서드가 호출된 프로세스가 종료될 때까지 block된다.

timeout 값이 양수(positive number)인 경우 최대 해당 시간 동안 block된다.

프로세스가 종료되거나 메서드가 시간초과된 경우엔 None을 돌려준단 점에 주의하라.

프로세스는 여러번 join될 수 있다.

교착 상태를 유발할 수 있으므로 프로세스는 자기 자신을 join할 수 없다.

프로세스가 시작되기 전에 프로세스에 조인하려고 하면 에러가 발생한다.

뭔소리지 이게.. 조인? 메서드?



2. 그래서 더 찾아보았다. 스택오버플로우에 올라온 글을 번역하였다.



질문자의 글 : 파이썬 멀티프로세싱 모듈의 .join() 메서드는 정확히 무슨 일을 하는거죠?

파이썬의 멀티프로세싱에 대해 공부하던 중 join() 메서드가 무슨 일을 하는지 알고 싶어졌다.

2008년의 튜토리얼에 따르면 "아래처럼 p.join()을 넣어주지 않으면

자식 프로세스는 유휴상태(idle)에 들어가고 종료되지 않아 좀비 프로세스가 되어

손수 kill해줘야만 하게 된다"고 한다.

from multiprocessing import Process

def say_hello(name='world'):
    print("Hello, %s" % name)

p = Process(target=say_hello)
p.start()
p.join()

테스트를 위해 time.sleep 동안에 PID를 출력하게 만들었고

프로세스는 알아서 종료되게끔 만들어보았다:

from multiprocessing import Process
import sys
import time

def say_hello(name='world'):
    print "Hello, %s" % name
    print 'Starting:', p.name, p.pid
    sys.stdout.flush()
    print 'Exiting :', p.name, p.pid
    sys.stdout.flush()
    time.sleep(20)

p = Process(target=say_hello)
p.start()
# no p.join()

그랬더니 20초 이내엔 다음과 같은 출력물이:

936 ttys000    0:00.05 /Library/Frameworks/Python.framework/Versions/2.7/Reso
938 ttys000    0:00.00 /Library/Frameworks/Python.framework/Versions/2.7/Reso
947 ttys001    0:00.13 -bash

20초 후엔 아래와 같이 출력됐다:

947 ttys001    0:00.13 -bash



출력 양상은 p.join()을 코드 맨 마지막에 추가했을 때와 같았다.
(p.join이 있던 없던 프로세스는 잘 종료됐다는 뜻)

Python Mole Of The Week엔 멀티프로세싱에 대해 이해하기 쉽게 설명해준 글이 있다.

"프로세스가 작업을 완료하고 종료될 때까지 기다리려면 join() 메서드를 사용하십시오"

하지만 겉보기엔 최소한 OS X가 어찌됐건간에 처리해주는 것 같이 보였다.

또한 이 메서드의 이름에 대해서도 궁금점이 생겼다.

메서드 이름인 .join()은 무언가를 '연결'시킨다는 것을 의미하는 것인가?

이 메서드로 인해 프로세스를 무언가의 끝에 이어주는 것인가? 아니면

이 이름은 파이썬이 가진 내장함수 .join()의 이름을 공유하는 것 뿐인가?



답변 :

join() 메서드는 threading이나 multiprocessing에서 쓰이는 메서드로,

파이썬의 str.join()과는 아무 상관이 없습니다.

이 메서드는 실제로 두 가지 이상의 무언가를 '연결'시켜주는 개념이라기보단

"이 쓰레드/프로세스가 완료될 때까지 기다린다"는 것을 의미합니다.

join이라는 이름이 붙게 된 이유는 multiprocessing 모듈의 API가 뜻하는게

threading 모듈의 API와 비슷하게 보였기 때문입니다.

threading 모듈에서 join은 해당 모듈의 Thread 객체에 사용되는 메서드명이었습니다.

그래서 join이라는 말은 일반적으로 많은 프로그래밍 언어에서

'스레드가 작업을 완료할 때까지 기다림'을 의미하게 되었습니다.

파이썬도 그저 그 관례를 차용했을 뿐입니다.



자 이제 질문자님이 본대로 join()을 호출한 것과 호출하지 않은 것 사이의

20초 딜레이에 대해 이유를 설명해드리죠.

메인 프로세스가 종료될 준비를 끝마쳤을 때(모든 작업이 끝났을 때)

기본적으로 이 프로세스는 암묵적인 룰을 통해

작동하고 있던 multiprocessing.Process 인스턴스에서

join()을 호출할 준비가 되어있습니다.

이 사실이 multiprocessing 문서에 명확하게 적혀있는 내용인 것은 아니지만

Programming Guidelines 섹션에는 다음과 같이 언급이 되어 있습니다.

"Remember also that non-daemonic processes will be automatically be joined"
(비-데몬성 프로세스들이 자동으로 join될 것이란 점도 기억하십시오)

사용자는 'daemon' 플래그를 통해 이 설정을 변경할 수 있습니다.

p = Process(target=say_hello)
p.daemon = Truep.start()
# Both parent and child will exit here, since the main process has completed.

이렇게 하면 자식 프로세스는 메인 프로세스가 완료되자마자 종료될 것입니다.



3. join은 '프로세스가 완료됨을 기다리다'라는 뜻이었다.

아래의 다른 답변에서 이 단어에 대한 어원을 설명한 글도 있어서 함께 끌어왔다.

<

개념은 이렇습니다, 여러개의 프로세스에 "forks"를 수행하는데

그 프로세스들 중 하나는 마스터이고 다른 것들은 노동자들인 셈이죠.

노동자들이 일을 끝마치면 그들은 마스터에게 합쳐(join)집니다.

>

Fork와 join은 멀티프로세싱에 사용되는 개념으로, 따로 읽어보아도 좋다.


2019-10-23

오토바이를 타고 출근하게 됐을 때 연비 계산

0. 일단 출퇴근 목적지까지 거리는 13.5km.

버스를 타고 다니는게 좋을까, 오토바이를 구하는게 좋을까.

계산해보기로 하였다.



1. 오토바이 기종은 무난하다고 알려진 가성비 깡패, 혼다 SCR 110 알파로 정했다.

신차 가격은 248만원.

중고 매물은 보배드림 사이트를 기준으로 했을 때

(2019-10-22 캡쳐)

이 정도의 가격이 형성되어 있었다.

이걸 보기 편하게 엑셀로 정리해보았다.



2. 엑셀

(대체적으로 1년에 20만원 가량의 하락이 있다.)

특이하게 값이 많이 떨어진 중고 물건의 경우엔 제품에 하자가 있는 경우가 있었다.

이걸로 스쿠터의 가성비는 증명됐다.

1년에 20만원씩 떨어지는 몸값 = 구매 후 n년 경과시 가격이 248 - 20n 만원에

형성되고 있던 것(중고 매물 몇 개만 보고 판단함).

물론 오토바이를 12년이나 타고 다닌다면야 뽕을 뽑고도 남겠지만...



3. 한편 버스를 타는 것은 다음과 같이 계산된다.

버스 1회 탑승비용이 1350원. 왕복 2700원.

주 5일 이용하고, 한 달은 4.3주라는 점을 감안했을 때(365 ÷ 7 ÷ 12)

1년 버스요금만 703,929원이다.



4. 이제 스쿠터의 소요비용을 더해보면

스쿠터는 기름값, 보험료, 각종 정비료 등 보수에 비용이 든다.


기름값부터 보자면

위에서 연평균 3000km정도를 타는 것으로 알 수 있다.

(맨 밑에 비정상적인 주행거리는 계산에 포함하지 않았다.

아마도 영업용으로 움직인게 아닐까 싶다.)

혼다 SRC110알파는 60km/h 정속주행시 56.4km/L의 공식 연비 스펙을 갖고 있다.

실제로는 그보다 덜 나올거라 생각하니 (계산도 용이할겸) 깔끔하게

50km/L로 계산하면 1년에 총 60L의 휘발유를 사용할거고,

현재 전국의 평균 유가는 1538원/L이므로

1년 기름값은 92,280원이다.



다음은 보험료.

출처에 따르면 매우매우 비싸게 레저나 출퇴근을 하고 싶다면

연 100만원에 마음 놓고 스로틀을 당길 수 있다

(하지만 덩달아 당겨지는 나의 수명...)

하지만 오토바이가 자동차를 때려박는다 하더라도 승용차 운전자가 다칠 일은 없으니

대물을 안 든다고 가정하면 대략 보험료 18만원에도 가능해진다.

1년 보험료는 만원 잡았다.

1년에 50만원 냈다.


다음은 유지보수 비용.













2019-10-22

내가 사용하는 PyCharm Live Template

0. 파이참에는 라이브 템플릿(Live Template)이라는 기능이 있다.

잘 활용하면 코딩을 한결 편리하게 할 수 있다보니

즐겨 사용하게 되는 기능 중 하나이다.



1. 기존에 추가되어 있는 템플릿은 아래와 같다.

(이미 추가되어 있는 나의 템플릿)

2. 이 중에서 내가 추가한 것은

open, printer, sur이다.

각각의 Template text는 다음과 같다.

open :
with open('$name$', '$mode$', encoding='utf-8') as $as$:
    $as$.
open 함수를 더 쉽게 이용할 수 있게 해준다.

printer:
print($SELECTION$)
선택된 블록을 print() 함수 안에 넣어준다. (해당 변수를 출력하고 싶을 때 사용함)

sur :
$with$($SELECTION$)
선택된 블록을 어떤 함수로 감쌀지 입력해줄 수 있다. (float()이나 int()로 감쌀 때 사용함)

main :

if __name__ == '__main__':
    $END$
if __name__ == "__main__" 구문을 자동완성해준다.



3. 사용법은 이렇다.

("op"까지 입력한 상태)

open이라는 built-in 함수가 있다보니 내가 추가한 템플릿과 함께 뜨는 것을 볼 수 있다.

여기에서 위의 open을 선택하면 평범한 자동완성이 되지만

맨 아래의 open을 선택하면 다음과 같이 템플릿이 생성된다

()

이 상태에서 입력하면 open 함수의 첫 번째 파라미터($name$)에 값을 입력할 수 있고

Tab키를 누르면 다음 파라미터($mode$)를 입력할 수 있다.

'이미 추가되어있는 나의 템플릿' 사진의 OK 버튼 위에 옵션을 보면

Tab키 대신에 어떤 키로 확장시킬 것인지도 선택할 수 있다.

사용자가 라이브 템플릿을 추가 및 수정할 수 있으니 매우 편리하다.



4. 단축키는 Ctrl + Alt + T 이다.

(커서를 대거나 블록설정한 뒤 단축키를 입력한 모습)

해당 hotkey를 입력하면 'Surround With'이라는 윈도우가 뜨게 된다.

간편하게 숫자 1부터 0까지, 그리고 a 키를 눌러서

기본적으로 탑재되어 있는 라이브 템플릿을 이용해볼 수도 있으며

p, s, q를 눌러서 내가 만든 라이브 템플릿을 사용할 수도 있다.

이제 블록 설정 후 Ctrl + X(잘라내기)는 그만.

스마트하게 라이브 템플릿을 활용해보자.



5. 추가 내용은 홈페이지에서 확인할 수 있다.

2019-10-21

matplotlib.mlab.normpdf --> scipy.stats.norm

0. matplotlib의 버전이 3.1.1이 되어가는 무렵... 해당 패키지를 이용해

histogram을 작성하기 위해 example들을 찾아보던 중이었다.

그러다 발견한 이 부분이 내 발목을 잡았다.

(코드)

위의 코드는 아래의 그래프를 그리는 코드이다.

(Histogram)

문제는 바로 이 부분.

# add a 'best fit' liney = mlab.normpdf(bins, mu, sigma)

뭐지 이게...



1. 구글에서 검색 * n회

일단 matplotlib.mlab은 매트랩(MATLAB)의 명령어와 호환되게끔

(같은 이름으로) 파이썬 함수를 작성해놓은 것들이다.

그래서 매트랩에 익숙한 사람들은 쉽게 사용할 수 있을 것이다.

많은 파이썬 함수들은 numpy와 scipy 라이브러리에서 찾을 수 있고

matplotlib에 남은 것은 스펙트럼 계산을 위한 것들 뿐이다.



그래서 matplotlib.mlab.normpdf같은 것들은 matplotlib 2까지는 있었지만

3에서는 사라진 듯하다.

정확하게는 사라질 예정이고, 아직 기능을 쓸 수는 있는 모양이다.

(대충 조만간 해당 기능이 scipy.stats.norm.pdf로 대체된다는 내용)

그래서 matplotlib 2.1.0까진 살아있었던 것이 흔적으로 남아있다.



2. 해당 함수는 x에 대한 정규 pdf를 리턴합니다.

Return the normal pdf evaluated at x; args provides mu, sigma.

pdf??

()

논문 볼 때마다 보이는 그 PDF?

가 아니라 다른 의미를 찾아보고 싶을 땐 항상 위키피디아를 먼저 뒤져본다.

또한 DeprecatedWarning을 던지던 위의 경고문에서도

scipy.stats.norm.pdf에서 관련 내용을 찾을 수 있으니

어서 달려가 읽어보았다.

matplotlib에서 PDF란 probability density function의 줄임말이다.



3. 그래서 이걸 왜 썼지.

아마도 best fit을 그린다는 의미가 '추세선'을 의미하는게 아니라

해당 랜덤 함수에 의해 생성된 수의 배열이 확률밀도함수와 동일하게 나와야

가장 이상적이기 때문일 것이다.

즉, histogram에서 데이터들의 추세선을 그린 것이 아니란 말씀.

2019-10-16

Pycharm 심볼 의미들

0. 파이참에서 심심하게 볼 수 있는 심볼들, 아래와 같은 것들이다.

(Numpy 패키지 아래엔 무수히 많은 것들이)

대체 옆에 있는 저 f나 v나 c는 무슨 의미인걸까.



1. 홈페이지에서 확인 가능하다. (아카이브 링크)


C는 클래스, m은 메서드, F는 함수, f는 필드

V는 변수, P(보라색)는 속성, P(노란색)는 파라미터 등을 의미한다.











2019-10-07

파이썬 리스트 원하는 크기로 자르기

0. 0부터 99까지 총 100개의 숫자를 3개씩 나눠야 할 필요가 있었다.

즉 리스트(l)에서 n개씩 뭉쳐서 다시 리스트로 만들어야 했다.

아래처럼.

[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14], [15, 16, 17], [18, 19, 20], [21, 22, 23], [24, 25, 26], [27, 28, 29], [30, 31, 32], [33, 34, 35], [36, 37, 38], [39, 40, 41], [42, 43, 44], [45, 46, 47], [48, 49, 50], [51, 52, 53], [54, 55, 56], [57, 58, 59], [60, 61, 62], [63, 64, 65], [66, 67, 68], [69, 70, 71], [72, 73, 74], [75, 76, 77], [78, 79, 80], [81, 82, 83], [84, 85, 86], [87, 88, 89], [90, 91, 92], [93, 94, 95], [96, 97, 98], [99]]



1. 해법은 간단했다.

딱 한 줄이면 끝난다.

[l[i:i + n] for i in range(0, len(l), n)]

어떻게 이런 코드가 나왔는지 살펴본다.

l = [i for i in range(100)]
n = 3
for i in (range(0, len(l), n)):
    print(i)

print([l[i:i + n] for i in range(0, len(l), n)])



2. 해설

[i for i in range(100)]

위의 코드는 리스트를 생성할 때 사용할 수 있는 '표현식(list comprehension)'이다.

원리에 관한 내용은 공개 강의인 코딩 도장에서 확인할 수 있다.

그렇게 0부터 99까지 총 100개의 정수가 담긴 리스트가 l이라는 변수에 바인딩 되어있다.
(실제로는 0부터 차례대로 생성해내는 제너레이터를 쓰지만 여기에선 편의상 이렇게 부르기로)

찬찬히 표현식의 뒷쪽부터 살펴보자.

range(0, len(l), n)

range() 함수를 이용해서 0부터 l의 개수만큼의 리스트를 만드는데

세 번째 아규먼트는 step 수를 나타낸다.

세 번째 값이 생략되어 있으면 0, 1, 2, 3, 4, 5...를 만들겠지만

세 번째 값이 만약에 3이라면, 0, 3, 6, 9, 12, 15...를 만들게 된다.

해당 값에 대한 앞의 표현식은

l[i:i + n]

이다.

즉, 리스트인 l에서 i번째부터 i + n번째 엘리먼트까지 슬라이싱하게 되므로

0:3, 3:6, 6:9, 9:12, 12:15... 와 같은 방식으로 슬라이싱이 이루어진다는 뜻이다.

파이썬은 끝 번호를 넣지 않으므로 [0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]... 처럼

깔끔하게 3개씩 슬라이싱이 가능해진다.

실로 간결하고 아름다운 표현식이 아닐 수 없다.