3장 동시성과 병렬성

차후 스레딩과 비동기 방식을 진행하기 위한 이론적 배경등을 소개한 챕터

다만 깊은 내용은 아니며 이정도가 있다 수준임. 이를 이해하기 위해선 더 파야한다..


제목은 거창하지만 사실 내용은 별게 없음.

왜냐면 맥북 받고 일주일도 안됬기 때문...


필자의 경우 윈도우만 30년을 써왔고

(물론 핸드폰과 타블렛은 아이폰/아이패드를 사용중)

맥쪽 세계는 먼 나라 이야기와 같았음.


간간히 아이패드를 키보드와 연결해서 쓸때마다 

'어휴 키 어지간히 불편하네' 를 외치며 맥북은 돈낭비다 라고 주장한 사람이었지만


어느새 나도 맥북을 사서 이러한 글을 쓰고 있음.


고민은 약 2달정도 했고 맥북으로 가게 된 가장 큰 이유는 아무래도 폰, 타블렛과의 연동을 극한으로 끌어올리기 위해서였음.

(물론 아직 극한으로 못 끌어올림 -_-)


현재 맥북 1주차인데 기회가 된다면 매주 맥북을 사용하는 느낌을 적으려고함


-1주차까지 한것

- 맥북 상태 검사(각종 액정검사 등)

- 리눅스 터미널 세팅 변경(bash-> zsh)

- 기타 필요 앱 깔기(카카오톡, 노션, 크롬 정도만 설치함)

- 느낀점 : 불편하다. 불편하다 불편하다...


-2주차까지 한것

- 여러 도움되는 앱을 설치함(지터치, 카라브루이너?, 마그넷, 등등)

- 듀엣, 리플렉터 등도 구매함

- 아이폰과 문자 동기화가 되면서 맥북을 사용하게 되는 경우 핸드폰을 굳이 찾을 필요가 없게됨

- 에어드랍을 쓰기 시작하면서 필요한 스샷들을 바로 아이패드로 옮길 수 있음(생각해보니 그냥 바로 아이클라우드로 동기화 해도 될듯?)

- 트랙패드가 너무 편함. 진짜 너무너무 편함.


- 5주차까지 한것

- 중간에 몇주가 사라짐 ㅠㅠ

- 맥 환경에 완전히 적응함

- 트랙패드는 정말 편한데 손목쪽에 무리가 가는 느낌도 듬. 간간히 마우스도 같이 사용해야할듯

- 터치바는 정말 쓸모가 1도 없다. 어떻게 사용하는지도 모르겠다.

- Sequel Pro가 좀 불안정한게 있어서 TeamSql을 깔아봤는데, 이것도 조금 불편한게 있음


- 6주차


- 구매때부터 가지고 다니던 템 세트.. 사실상 거의 3kg 되는듯 전체 다 합치면...

2장은 기본문법으로 for문, try/except/else, 데코레이터, 제너레이터, 동일성 등이 수록되어있다.

제일 핵심적인 부분은 아무대로 데코레이터, 제너레이터 인듯.


데코레이터의 경우 만들기도 간단하며 기능은 매우 강력하기에 꼭 알아야할 기능이고

제너레이터의 경우 이해는 됬지만 이거를 어디에 사용해야할 지는 감이 안잡혔다.



정리 보러 가기
정리하는게 티스토리보다 notion이 편하다 보니 이상하게 운영을 하기 시작.
아무튼 이제는 티스토리에 직접 안써야지..

노션 정리 글 보러가기


제 1장 철학과 개념


코드는 개발 할때 보는 것 보다 그냥 읽어야 하는 경우가 더 많다...(개발할때보다 개발 후에 더 많이 본다는 뜻인듯)

그렇기에 코드의 생명은 가독성이며 파이썬 다운 코딩은 바로 이 가독성에 집중 되어 있다.


boolean을 비교함에 있어서

if boolean == True 보다는 if boolean is True 이런식으로 쓰는 것이 파이썬 다운 코딩이다.


1.2 파이썬 변수 범위(SCOPE)


Local, Enclosed, Global, built_in


위 순서대로 파이썬은 변수를 찾는다.



msg = "global"

def test():
    msg = "local"
    print(msg)
# 위 경우 프린트할 msg를 우선적으로 로컬에서 찾으므로 local이 인쇄된다


msg = "global"
def test():
    msg+= " variable"
    print(msg)
# 위 경우 변수를 찾을 수 없다는 에러가 발생한다. local에서 우선 msg를 찾고 난 후에 더하려고 하지만 msg가 없기 때문이다.


def test():
    global msg
    msg+= " variable"
    print(msg)

# 이렇게 하면 사용이 된다. global의 의미는 이 msg는 전역변수야 라고 선언하는 것이다. 그렇기에 msg += 가 가능하여 global variable가 출력된다
# 다만 위 코드처럼 사용할 경우 실제 전역변수 msg = "global" 의 내용물도 변하게 된다.(global variable가 된다)

이번에 맡게 된 기능중 되게 쉽게 생각했는데 엄청 고생하고 있는 부분이 있다.


원리는 간단하다.

클라이언트가 요청을 보내고 서버는 응답을 보낸다. 이때 클라이언트로 가는 응답 이외에 다른 내용을 다른 브라우저에 띄워야 한다.

예를 들어 클라이언트가 정보를 요청하면 서버는 해당 클라이언트의 정보를 응답하기에 앞서 서버 브라우저에 요청이 왔음을 알리고 과정을 띄운다.

(과정이 진행되면 계속 내용을 추가해야한다)

라이브 콘솔 같은 개념이다.


여기서 문제가 되는 부분이 바로 저 서버 브라우저이다.


http 통신은 기본적으로 요청과 응답, 이렇게 한쌍이 존재해야한다. 하지만 위와 같은 과정의 경우엔 저 서버 브라우저는 요청한 사람이 없는데

응답을 받게되는 상황이 된다.


그래서 첫번째로 접근한 방법은 서버 브라우저가 response를 알아챌 수 있는가 였다. => 불가

html에 jquery를 암만 찾아봐도 response를 알아낼 방법은 없었다...


두번째로 접근한 방법은 정말 무시한 방법... 호출이 올때마다 서버 브라우저 내용을 가져와서 플라스크 서버에 캐싱을 한 후에

새로 들어온 내용을 추가 한 후 렌더링 하는 것이다...

무식무식..


세번째 방법은 채팅 프로그램처럼 socketio를 사용하는 방법이다.

현재까지는 이 방법이 제일 주효하다. 일종의 채팅과 같은 로직이기 때문이다.


네번째 방법은 polling, sse를 이용하는 방법.

polling, SSE는 위에서 언급했던 요청/응답의 한계를 벗어나 실시간으로 정보를 띄우기 위해 만들어진 방법들이다.

그렇기에 이를 적용하려 했으나 내 이해력이 문제가 되어 수많은 뻘짓 후 포기하였다.


socketio 같은 경우엔 한번 소켓만 연결하면 편하게 계속 정보를 보내면 됬으나 polling, SSE의 경우엔 url을 통해서 연결을 해줘야했다..

그리고 이후에 계속 서버에 요청을 하게끔 무한루프? 처럼 로직을 짜야하는데 서버상에서 내가 이를 어떻게 처리해야할지 이해가 안되다 보니

포기하게되었다.


좀더 찾아본 후에 예제 코드와 추가된 설명을 업데이트 해야지..



웹 상에서 사용가능한 시각화 라이브러리 중 D3를 좀더 이해하기 쉽게 만든것이 C3이다

다양한 그래프 모양을 비교적 손쉽게 사용할 수 있다.


이번에 수정한 것은 tooltip 부분을 보여주는 것을 바꾸는 것이다.

같은 x 값이라도 서로 다른 모양을 갖은 stacked bar에서 마우스 위치에 따라 서로 다른 내용이 나오게끔 하는 것이 목적이다.



finddle

위 링크로 가면 코드를 확인 할 수 있다.
추가로 ttile 부분을 수정하면 결과 값을 보여주는 식으로도 수정이 가능하다.


입국심사.

문제:

입국하는 사람수 : n

심사대 :[심사시간, 심사시간..]

으로 하여 최소 입국 심사 시간을 구하는 것이다.

예를 들어 6, [7,10] 일 경우, 6명을 2개의 심사대에서(7분, 10분 심사 걸림) 심사를 할 경우 제일 빠른 경우가 언제인지를 알아내야한다.


이진탐색 문제로 분류가 되어있는데 처음에 풀때는 이걸 그렇게 접근 못했고 결국 다른 사람들의 해설을 확인할 수 밖에 없었다.

이 문제를 처음에는 동적계획으로 풀거나 아니면 횟수 구분을 통한 변수 조절 등.. 다르게 생각해봤는데 사실상 손으로도 풀리지가 않아서...


핵심은 다음과 같다.


위 예시의 경우 최악의 시간은 60분이다.(6명이 전부 10분 걸리는 심사대로 갔을 경우를 뜻한다)

그리고 상징적인 값인 1분을 상정한 후 1분과 60분 사이의 중간값, 30분을 정한다.


이제 우리에겐 30분이란 시간이 있으니 이 30분 동안 과연 몇명을 입국심사 할 수 있는지 계산 해본다.

계산 방법은 단순하다. 30분 // 7 + 30분 // 10이면 된다. 


즉 30분 동안 7분 심사대는 4명을 심사할 수 있고 10분 심사대는 3명을 심사할 수 있으므로 총 7명을 해결 할 수 있다.

그렇다면 다시 문제로 돌아가보자. 문제에서는 6명이 대기자로 제시되어있다.

우리는 30분의 시간동안 7명을 심사 할 수 있다. 7명 > 6명, 즉 30분은 매우 충분한 시간이다.

그렇다면 우리는 30분보다 작은 값을 주고 똑같은 방법을 통해서 해당 시간이 해결할 수 있는 인원수를 구해볼 수 있다.


제일 처음에는 1, 30, 60이었으나 이제는 시간을 줄여야 하므로 1, ?, 30이 된다.

그럼 이제 중간 값은 15가 되고 15 //7 + 15// 10 은 3명이 된다.

3명은 6명에 한참 모자라므로 15분이란 시간은 매우 부족한 시간임을 알 수 있다.


이제는 15, ? , 30이 된다. 

중간값은 22가 될 것이고 22분 동안 해결할 사람 수는 22//7 + 22//10, 5명이 된다.

이 역시 매우 부족하므로 시간이 더 필요하다


이제는 22 ? 30 이 될 것이고 중간 값은 26, 26분동안 해결할 사람 수는 26 //7 + 26//10으로 5명이 된다.

아직도 부족하다


26 ? 30에서 중간 값은 28이 된다. 28//7 + 28//10 은 6명이다.

6명은 문제에서 제시한 사람수이다!! 그런데 혹시 모르니 27로도 해보자...


이런 방식으로 이 문제를 풀면 된다.

결국 최악의 시간(max_time)으로 부터 아래로 줄여가면서 해당 시간대가 소화할 수 있는 사람수를 찾는 것이고

하나씩 내리게 되면 O(N) 시간이 들지만 이진탐색을 하게되면 O(logN)이므로 훨씬 빠르게 가능하기에 이진 탐색으로 접근 하는 것이다.




def solution(n, time):
    mid = n/2 * max(time) # 최악의 시간의 경우
    Left = 1
    Right = n * max(time)
    while Left <= Right:
        tot = 0
        for i in time:
            tot += mid // i
        if tot == n:
            temp_tot = 0
            for k in time:
                temp_tot += (mid-1)//k
            if temp_tot == n:
                return mid-1
            return mid
        if tot > n:
            Right = mid - 1
        else:
            Left = mid + 1
        mid = (Right + Left) // 2
        
if __name__ == "__main__":
    print(solution(6, [7,10]))


라고 해설을 위해서 썻지만 정작 내 코드는 실패만 주르극...아무래도 종료조건에 뭔가 다른게 더 필요해 보이는데..지금 내눈엔 안보인다.

SQL 쿼리를 sqlalchemy로 바꾼 방식을 틈날때마다 하나씩 추가하려함


# update


# 예) 게시판 테이블에 회원번호 칼럼이 있고 이를 회원 테이블로부터 가져와서 해당 회원에 맞게 업데이트 해야하는 상황이다
# update bbs set user_num = (select user_num from user where user.id = bbs.id)

#UPDATE table_one SET column_one = (SELECT column_one FROM table_two WHERE table_one.column_two = table_two.column_two)
query = session.query(table_one).filter(table_onw.column_two == table_two.column_two)
query = query.update({"column_one": table_two.column_one}, synchronize_session=False) # synchronize_session이 핵심!

python-flask에서 DB와의 커넥션은 엔진을 만들고 이를 바탕으로 session을 가져오는 방식으로 진행한다


# Engine을 통해서 session을 가져온다
Engine = create_engine( ~~~~~)
Session= scoped_session(sessionmaker(~~~~~, bind = Engine)

#session을 통해서 테이블의 데이터를 가져올 수 있다.
session.query(table).all() # 1) 해당 테이블 전체  가져오기
session.query(table).first() # 2) 해당 테이블에서 한줄만 가져오기

# 내가 해당 테이블의 모든 칼럼 정보가 필요로 하다면 위에처럼 가져오는게 맞다. 하지만 특정 칼럼 정보만 필요하다면 
session.query(table.column_one).first() # 3) 이런식으로 특정 칼럼을 명시해주는 것이 퍼포먼스 상으로 좋다.

# 하지만 만약에 특정 칼럼만 가져와서 데이터 확인 후 이를 업데이트 해야하는 경우가 있다면 위의 3)의 경우를 사용할 수 없다.

row = session.query(table).first() # 4)

row.column = new_value 
or 
setattr(row, column, new_value) 

# 보통 session으로 가져왔을 경우의 update 방식이다. 이 경우는 table에서 특정 칼럼을 지칭하지 않았다.
# 하지만 이럴 경우 위에서 언급했듯이 퍼포먼스의 이슈가 있고 좀더 빠르게 처리하기 위해선 특정 칼럼 가져오기 + update를 해야한다.
# 3)에서 했던 방식으로 update를 할 경우.. 즉

row = session.query(table.column).first() # 5)
row.column = new_value or setattr(row, column, new_value)
# 위 방식으로 할 경우 업데이트가 되지 않는다.
# 왜냐하면 4번의 결과값과 5번의 결과값은 확연히 다르기 때문이다.

# 4번은 colletion_result이므로 setter가 존재한다. 하지만 5)번은 4번과 모양은 같지만 타입은 tuple에 불과하다. 그렇기에 setter가 존재하지 않는다.
# https://stackoverflow.com/questions/51391039/cant-set-attribute-on-result-objects-in-sqlalchemy-flask 를 보면 자세히 알 수 있다.

# 그렇다면 특정 칼럼만 가져오면서 update를 할 수 있는 방법은 없을까?
# rom sqlalchemy.orm import load_only 를 사용하면 된다.

query = session.query(Table).options(load_only("column_one", "column_two")) # 6)
# 위 방법은 컬럼을 단 두개만 가져온다. 또한 setter를 가지고 있기에 update를 사용할 수 있다.
# 실제 0.76 정도 걸리던 실행시간이 0.40 정도로 줄일 수 있었다.

+ Recent posts