"프로그래밍 언어의 개념과 흐름에 대한 고찰 - 파이썬답게 코딩하기" 학습중...


multiprocessing 기법에 대해 어렴풋이 알거나 몰랐던 내용들 몇가지 정리해봅니다.



## 스레드에서 사용하던 Lock, RLock, Condition, Semaphore등도 multiprocessing 모듈에서 사용 가능, 구현 방법도 동일
## 그러나 일반적으로 multiprocessing 모듈에서는 사용할 필요없다. why? multiprocessing 모듈에서는 메모리 공간이 분리.
## 스레드나 프로세스와 같이 동시성을 활용한 로직 설계시 메모리 공유하지 않는게 좋다. 그러나 메모리를 공유할 경우 안전장치가 필요
## multiprocessing 모듈은 Value, Array라는 API 제공하여 메모리의 무결성 보장한다.

# import multiprocessing
#
# def worker(num, num_list) :
# p = multiprocessing.current_process()
# print("[%s] num : %s" % (p.name, num.value))
# for idx, value in enumerate(num_list) :
# print("[%s] num list[%s] : %s" % (p.name, idx, value))
# num.value = 50
# for i in range(len(num_list)) :
# num_list[i] = num_list[i] * 10
#
# def main() :
# single_integer = multiprocessing.Value("i", 5) ## i 5로 초기화.
# integer_list = multiprocessing.Array("i", range(10))
# p = multiprocessing.Process(name = "worker", target = worker, args = (single_integer, integer_list))
# p.start()
# p.join()
# print("num : %s" % (single_integer.value))
# for idx, value in enumerate(integer_list) :
# print("num list[%s] : %s" % (idx, value))
#
# if __name__ == "__main__" :
# main()



(실행 결과)


# [worker] num : 5
# [worker] num list[0] : 0
# [worker] num list[1] : 1
# [worker] num list[2] : 2
# [worker] num list[3] : 3
# [worker] num list[4] : 4
# [worker] num list[5] : 5
# [worker] num list[6] : 6
# [worker] num list[7] : 7
# [worker] num list[8] : 8
# [worker] num list[9] : 9
# num : 50
# num list[0] : 0
# num list[1] : 10
# num list[2] : 20
# num list[3] : 30
# num list[4] : 40
# num list[5] : 50
# num list[6] : 60
# num list[7] : 70
# num list[8] : 80
# num list[9] : 90




## server process => manager API, 코드상으로는 server process는 보이지 않는다. 내부적으로 manager API를 담당하여 처리.

# import multiprocessing
#
# def print_array_or_list(name, values) :
# for idx, value in enumerate(values) :
# print("[%s] num list[%s] : %s" % (name, idx, value))
#
# def worker(v, a, l, d) :
# p = multiprocessing.current_process()
# print("[%s] value : %s, dict : %s" % (p.name, v, d["key"]))
# print_array_or_list(p.name, a)
# print_array_or_list(p.name, l)
# v.value = 50
# for i in range(len(a)) :
# a[i] = a[i] * 10
# for i in range(len(l)) :
# l[i] = l[i] * 10
# d["key"] = "Python3"
#
# def main() :
# manager = multiprocessing.Manager()
# v = manager.Value("i", 5)
# a = manager.Array("i", range(10))
# l = manager.list(range(10))
# d = manager.dict()
# d["key"] = "Python2"
# p = multiprocessing.Process(name = "worker", target = worker, args = (v, a, l, d))
# p.start()
# p.join()
# main_name = "main"
# print("[%s] value : %s, dict : %s" % (main_name, v, d["key"]))
# print_array_or_list(main_name, a)
# print_array_or_list(main_name, l)
#
# if __name__ == "__main__" :
# main()




(실행 결과)


# [worker] value : Value('i', 5), dict : Python2
# [worker] num list[0] : 0
# [worker] num list[1] : 1
# [worker] num list[2] : 2
# [worker] num list[3] : 3
# [worker] num list[4] : 4
# [worker] num list[5] : 5
# [worker] num list[6] : 6
# [worker] num list[7] : 7
# [worker] num list[8] : 8
# [worker] num list[9] : 9
# [worker] num list[0] : 0
# [worker] num list[1] : 1
# [worker] num list[2] : 2
# [worker] num list[3] : 3
# [worker] num list[4] : 4
# [worker] num list[5] : 5
# [worker] num list[6] : 6
# [worker] num list[7] : 7
# [worker] num list[8] : 8
# [worker] num list[9] : 9
# [main] value : Value('i', 50), dict : Python3
# [main] num list[0] : 0
# [main] num list[1] : 10
# [main] num list[2] : 20
# [main] num list[3] : 30
# [main] num list[4] : 40
# [main] num list[5] : 50
# [main] num list[6] : 60
# [main] num list[7] : 70
# [main] num list[8] : 80
# [main] num list[9] : 90
# [main] num list[0] : 0
# [main] num list[1] : 10
# [main] num list[2] : 20
# [main] num list[3] : 30
# [main] num list[4] : 40
# [main] num list[5] : 50
# [main] num list[6] : 60
# [main] num list[7] : 70
# [main] num list[8] : 80
# [main] num list[9] : 90
## 스레드와 달리 대부분의 API가 프로세스와 스레드에서 자원의 무결성을 보장해주므로 비교적 쉽게 사용가능.




## Process Pool
## 어떤 작업과 작업에 필요한 데이터를 pool에 등록하고 pool에서 사용할 프로세스의 개수를 입력하면 프로세스의 pool에서 작업과
## 작업에 필요한 데이트를 나눠서 정재진 프로세스만큼 작업을 나눠서 처리.

# import multiprocessing
#
# def print_initial_msg() :
# print("start process : %s" % multiprocessing.current_process().name)
#
# def worker(data) :
# return data * 2
#
# def main() :
# pool = multiprocessing.Pool(processes = 4, initializer = print_initial_msg)
# data_list = range(10)
# result = pool.map(worker, data_list)
# pool.close()
# pool.join()
# print("result : %s" % result)
#
# if __name__ == "__main__" :
# main()



(실행 결과)


# start process : SpawnPoolWorker-3
# start process : SpawnPoolWorker-4
# start process : SpawnPoolWorker-2
# start process : SpawnPoolWorker-1
# result : [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
## 4개의 프로세스에서 병렬 처리된 작업의 결과가 하나로 합쳐져 출력. pool은 단순 반복 작업등을 손쉽게 처리. 빅데이터 처리에 유용.




## Coroutine
## 협력형 멀티태스킹, 이전의 스레드트 멀티프로세싱은 선점형.
## 일반적으로 함수는 한번 실행되고 종료되나 코루틴은 여러번 실행되고 여러번 종료된다.
## yield를 사용해서 여러번 반복될 수 있다.
## function, generator, coroutine.

# def coroutine() :
# while True :
# msg = yield ## yield를 넣어 값을 입력받을 수 있도록 한다.
# print("hello your input message is '%s'" % msg)
#
# def main() :
# c = coroutine()
# next(c)
# next(c)
# c.send("test") ## send함수를 통해 호출.
# c.send("coroutine")
#
# if __name__ == "__main__" :
# main()



(실행 결과)


# hello your input message is 'None' ## yield에 아무런 값을 전달하지 않고 next 함수를 실행함.
# hello your input message is 'test'
# hello your input message is 'coroutine'




# import random
#
# TOTAL_WORK_LOAD = 50
#
# def worker() :
# total_work_load = 0
# worker_name = ""
# while True :
# worker_name, total_work_load = yield (worker_name, total_work_load) ## yield로 괄호안의 값을 입력받는다.
# worker_load = random.randrange(1,10)
# worker_load = worker_load if total_work_load >= worker_load else total_work_load
# total_work_load -= worker_load
# print("[%s] total : %s, work : %s" % (worker_name, total_work_load, worker_load)) ## yield로 받은 값을 반환
# yield total_work_load
# ## yield로 입력과 출력을 모두 사용.
#
# def main() :
# w1 = worker()
# w2 = worker()
# ret = TOTAL_WORK_LOAD
# while ret > 0:
# next(w1)
# ret = w1.send(("w1", ret))
# next(w2)
# ret = w2.send(("w2", ret))
#
# if __name__ =="__main__" :
# main()



(실행 결과)


## 협력형 멀티태스킹으로 하나의 작업이 끝나고 반환된 값으로 이어서 작업을 하도록 한다.
# [w1] total : 41, work : 9
# [w2] total : 36, work : 5
# [w1] total : 33, work : 3
# [w2] total : 24, work : 9
# [w1] total : 16, work : 8
# [w2] total : 10, work : 6
# [w1] total : 7, work : 3
# [w2] total : 4, work : 3
# [w1] total : 0, work : 4
# [w2] total : 0, work : 0




# def return_one_to_ten() :
# for i in range(10) :
# yield i
#
# def get_coroutine() :
# yield return_one_to_ten()
#
# def main() :
# print("== get coroutine ==")
# c = get_coroutine()
# print(c)
# print("== get coroutine's return value ==")
# ret = next(c)
# print(ret)
# print("== get values ==")
# print(next(ret))
# print(list(ret))
#
# if __name__ == "__main__" :
# main()



(실행 결과)


# == get coroutine ==
# <generator object get_coroutine at 0x0000026D266110F8>
# == get coroutine's return value ==
# <generator object return_one_to_ten at 0x0000026D26611150>
# == get values ==
# 0
# [1, 2, 3, 4, 5, 6, 7, 8, 9]




## 앞의 예제를 yield from 구문을 추가하여 만들 경우

# def return_one_to_ten() :
# for i in range(10) :
# yield i
#
# def get_coroutine() :
# yield from return_one_to_ten()
#
# def main() :
# print("== get coroutine ==")
# c = get_coroutine()
# print(c)
# print("== get values ==")
# print(next(c))
# print(list(c))
#
# if __name__ == "__main__" :
# main()



(실행 결과)


# == get coroutine ==
# <generator object get_coroutine at 0x00000179871810A0>
# == get values ==
# 0
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
## 실행 결과 동일하다
## yield문이 포함된 제네레이터 함수를 실행하면 제네레이터 객체가 반환되는데 이 때는 함수의 내용이 실행되지 않는다.
## next()라는 빌트인 메서드를 통해 제네레이터를 실행시킬 수 있으며 next() 메서드 내부적으로 iterator를 인자로 받아 이터레이터의
## __next__() 메서드를 실행시킨다.



+ Recent posts