# Advanced
Python은 정말로 thread가 없을까?
---
- 정확히 말하면 thread를 허용하지 않는다. 그 이유는 GIL (Global Interpreter Lock) 때문이다.
- GIL (Global Interpreter Lock)이란, python에서는 하나의 프로세스 안에 모든 자원의 lock 을 글로벌하게 관리함으로써 한번에 하나의 쓰레드만 컨트롤 하도록 동작한다. 즉, thread 를 동시에 수행하더라도 결국 GIL 때문에 한번에 하나의 쓰레드를 사용해서 계산을 하게 되어 수행 시간은 비슷하게 나온다
- GIL 덕분에 GC와 같은 가비지 컬렉팅은 더 쉽게 구현할 수 있지만, 멀티 코어 시대에 CPU 활용이 아쉬운 것은 사실임.
- 하지만, 그렇다고 Python에서 threading 이 의미가 없는 것은 아니다. GIL 이 적용되는 것은 CPU 동작에서만 해당되고, **I/O 작업을 할 때는 다른 thread 가 cpu 동작을 할 수 있다.** 따라서 I/O bound 작업일 때 threading 을 사용하면 효과적일 수 있다.
```python=
# Threading 사용법
from threading import Thread
# multiprocessing 사용법
from multiprocessing import Process, Queue
```
- Python에서는 threading 을 사용할 때, `from threading import Thread` 로 사용한다. 여기서 주의해야할 것이, Python 코드 레퍼런스를 보면 주로 threading 보다는 multiprocessing 을 사용하는 것을 볼 수 있다. 이것은 쓰레드 대신에 프로세스를 생성하여 병렬처리를 하도록 한다.
- 각각의 목적에 따라서 사용할 수 있는 threading 의 경우 CPU 작업이 적고 IO 작업이 많은 IO bound task 인 경우 사용할 수 있고, multiprocessing 의 경우 CPU 작업을 할 수 있고 이것을 (이것은 멀티태스킹, 즉, CPU 하나가 일을 여러 개 쪼개서 돌아가면서 수행하는 것) 을 통해서 병렬 처리를 가능하게 한다.
> 출처: https://monkey3199.github.io/develop/python/2018/12/04/python-pararrel.html
Python의 메모리 관리와 GC
---
- Python에서는 모든 객체가 reference count 를 갖고 있다. 따라서 reference count = 0 일 때 사용하지 않는 메모리로 간주하는데, GC 가 일정 주기로 reference count = 0 인 객체들을 정리해준다
Python의 iterator 와 generator
---
- `Iterator` 는 **요소가 복수인 컨테이너 (list, tuple, set, dict, string)** 에서 각 요소를 꺼내어 어떤 처리를 할 수 있도록 하는 간편한 방법을 제공하는 객체이다.
- `Generator` 는 iterator 의 한 종류로 하나의 요소를 꺼내려고 할 때마다 요소 generator 를 수행하는 타입으로 python 에서는 `yield` 구문으로하는 것이 일반적이다.
- Yield 는 python 에서 제공하는 키워드로 generator 를 반환하는 키워드다. Generator 는 여러 개의 데이터를 미리 만들어놓지 않고 필요할때마다 즉석에서 하나씩 만들어낼 수 있는 객체를 의미한다. 이것은 성능상의 이슈가 있는 것으로 일반적으로 iterator 가 데이터를 반환할 때 하나씩 반환하는 것이 아니라 한꺼번에 반환하는데 이렇게 대기하는 시간을 방지하기 위해서 generator 를 사용한다
- Generator 에서 yield 를 사용하는 방법 외에 yield comprehension 이라는게 있다. List compreshension 하고 동일하고, 다른 점은 () 소괄호를 사용한다는 점이 다르다고 볼 수 있다.
> - https://engineer-mole.tistory.com/64
> - https://www.daleseo.com/python-yield/
Iterator 와 Iterable 의 차이
---
- 예를 들어 list는, Iterator Protocol 에서 정의한 iterable 구현 요구사항이 충족되어 있다고 본다 . 이 말은 즉 프로토콜을 준수한 클래스를 만든다면 (dict, list 등 미리 정의된 것이 아닌), 우리만의 iterable 과 Iterable 을 커스터마이징해서 사용할 수 있다는 뜻이다
- Iterable은 순회할 수 있는 모든 객체를 가리킨다. 특징으로는 __iter__라는 추상메소드를 실제로 구현해야하면 이 메소드는 호출될 때마다 새로운 Iterator 를 반환한다. 자료구조나 클래스가 Iterable 이기 위해서는 클래스에 __iter__ 메소드가 구현되어있으면 iter() 라는 내장함수를 통해서 Iterator 를 생성할 수 있다.
```python=
class MyClass:
def __init__(self):
self.arr = range(0, 10)
def __iter__(self):
for item in self.arr:
import time
time.sleep(0.5)
yield item
mClass = MyClass()
for item in mClass:
print ("item from iterator: ", item)
```
- Iterable 은 for문에 넣을 수 있는 모든 값인 반면, Iterator 는 상태를 유지하며 반환할 수 있는 마지막 값까지 원소를 필요할때마다 하나씩 반환하는 것. Iterable 은 매 iter 마다 새로운 Iterator 를 생성한다. 이 때, 각 Iterator 는 서로 다른 상태를 유지하고 있다. 다시 말해, 한 Iterator 의 동작이 다른 Iterator 의 동작에 영향을 끼치지 않는다는 것이다. Iterator 는 어떤 상태 값을 갖고 있어서 (정확히는 다음 반환값의 상태가 계속 업데이트 되는 것이다)
- Iteroator 는 `__iter__` 를 구현하되 자기 자신을 반환하도록 self 구현해야 한다.
- `__next__` 메소드를 구현해서 Iterator 를 next 내장 함수의 인자로 줬을 때 다음에 반환할 값을 정의한다
- Iterator 가 더 이상 반환할 값이 없는 경우 StopIteration 예외를 발생시킨다
- Iterable 로 선언된 리스트를 루프 돌 때, __iter__ 함수에서 Iterator 를 가져온 후 __next__ 함수를 호출하도록 되어있다.
```python=
iterator = iter([1, 2, 3, 4, 5])
print(next(iterator))
print(next(iterator))
print(next(iterator))
```
> 출처: https://engineer-mole.tistory.com/64 [매일 꾸준히, 더 깊이:티스토리]
- Iterator 는 일회용 깡통 같아서 값을 모두 사용했다면 재사용은 할 수 없다. Iterator 는 Iterable 의 자식 클래스이고 Iterable 의 상속을 받는다. 또한, Generator 는 Iterator 의 자식클래스임.
(cf) 각 자료구조의 iterable, Generator, Iterator 상속 여부
```
- list는 Iterable를 상속 받나요? True
- list는 Iterator를 상속 받나요? False
- list는 Generator를 상속 받나요? False
- tuple는 Iterable를 상속 받나요? True
- tuple는 Iterator를 상속 받나요? False
- tuple는 Generator를 상속 받나요? False
- set는 Iterable를 상속 받나요? True
- set는 Iterator를 상속 받나요? False
- set는 Generator를 상속 받나요? False
- dict는 Iterable를 상속 받나요? True
- dict는 Iterator를 상속 받나요? False
- dict는 Generator를 상속 받나요? False
```
> 출처: https://engineer-mole.tistory.com/64 [매일 꾸준히, 더 깊이:티스토리]
map 과 dict 의 차이
---
- dict 는 hash table 구조를 갖는 자료구조다.
- map () 은 함수로, 함수의 인자로는 map을 수행할 함수 - *예를 들어, 람다 함수* - iterator (리스트 뿐만 아니라 반복가능한 iterator를 받는다. Iterator 를 함수에 맵핑을 한다는 의미이고, 이를 list 로 변환해서 쓰는 경우가 종종 있다.
- 애초에 map 이 c++에서의 맵을 의미하는게 아니라서 비슷한 대상이라고 보기 어렵다.
- map-reduce 라는 말이 있듯이, map() 이 있으면 reduce() 함수도 있다. Reduce 함수도 map 과 동일하게 사용되는데, 누적합과 같은 결과를 도출할 때 사용된다. Filter 라는 것도 있는데, iterator 에서 filter 할 함수를 적용해서 결과를 도출한다.
> 출처: https://h2hyun37.tistory.com/15
Lambda 함수
---
```python=
b = [1,2,3]
list(map(lambda x: x**2, b))
# output: [1, 4, 9]
```
deque() vs list()
---
- stack 은 배열 또는 `deque` 으로 구현할 수 있는데, deque 으로 유리한 점이 s 가 클 때 item 이 메모리에 같은 위치에 저장된다는 장점이 있다.
- 큰 차이는 list 나 deque 의 가장 앞에 데이터를 추가할 때이다. List 는 insert(0, element) 으로 추가하는데 걸리는 시간이 O(n) 이다. - element를 하나씩 이동하는 시간이 소요된다. - 하지만, deque의 경우 appendLeft() 를 사용하면 O(1) 의 시간이 걸린다 (popleft 도 마찬가지) [자세히](https://wellsw.tistory.com/122)
```python=
from collections import deque
```
- 그 원인은 구현된 자료구조에서 찾을 수 있다. deque 은 **doubly linked list** 이고, list 는 배열이다. linked list와 array의 차이와 동일하게 list는 랜덤 access에 대해서는 더 좋은 performance 를 보여줌. [자세히](https://stackoverflow.com/questions/22340087/python-deque-difference-from-list)
- Python에서 list 는 고정된 크기의 메모리 공간을 할당하고 사용하기 때문에 random access가 가능하고 deque 보다 더 적은 메모리를 사용한다. 하지만 append 할 때를 제외하고 데이터를 insert 할 때 추가적인 메모리 재할당 (reallocation) 이 필요하다. 여기서 reallocation 은 새로운 memory block 할당이 아니라 값을 하나씩 뒤로 미룬다던지의 작업을 의미하는 것 같다.
- 조금 더 advanced 한 내용으로 deque 는 **append 와 pop 할 때 thread-safe** 하다. 왜냐하면 Cpython 으로 구현된 deque 은 atomic 하게 동작하기 때문이다. [자세히](https://dev.to/v_it_aly/python-deque-vs-listwh-25i9)
thread, async 에 대해서
---
#### Python 에서 비동기 프로그래밍이란?
- Ayncio 란 더 넓은 의미로 보면 concurrency 에 대한 이야기임. Concurrency 란 프로그래밍을 할 때 동시에 여러 개의 일을 실제로 또는 그런 것처럼 보이게 처리하는 방법이다. Concurrency 에는 3가지가 있다. Multiprocessing, multihreading 그리고 aynchronous programming 이다
- I/O (또는 wait 이 필요한 어떤 작업) 요청 후 기다리는 시간이 있다. 이럴 때 다른 작업을 처리하면 어떨까? 라는 생각. CPU 를 놀게 하지 말자. CPU bound 작업에 대해서는 multiprocessing 을, IO bound 작업에 대해서는 multithreading 과 asyncio 를 사용하자. Default 로 asyncio 는 one thread 이고, 이는 race condition 문제에서 자유롭게 한다. 반면에 multithreading 은 lock 문제와 race condition 문제를 고려해야 한다.
#### Asyncio 와 threading 의 비교
```
- Asyncio 가 더 안전하다:
- Ayncio 가 더 가볍다
- Asyncio 가 네트워킹에 더 유용하다
- MT 가 file system 작업할 때 더 유용하다
- 많은 python library 들은 asyncio 친화적이지 않다
- Asyncio 는 항상 코드를 더 빠르게 해주지 않는다
- MT 는 디버깅하기 힘들다
- MT 는 resource intensive 하다
- MT는 대용량의 concurrency 에서 비효율적인 모델이다
```
- 결론은 **use asyncio when you can, use threading when you must**
> 출처: https://dev.to/v_it_aly/asyncio-basic-fundamentals-4i5m