본문 바로가기
Robot/22년도 자율주행 테크니션 양성과정 일지록

[파이썬] DAY16 네트워크 계층 & 스레드 동작 제어

by 9루트 2022. 2. 9.

통신 관련 코드 2개 주실 거임

 

채팅 프로그램(스레드 쓴 것)

다 잡을 수 있음 , 이미 채팅 중인 사람과 채팅 가능

 

채팅 프로그램(스레드 안 쓴 것)

 

통신 묶어서 설명해드릴거임

 

 

1. 스레드 

동시성과 병렬성(꼭 알고 가자)

동시에 끝나기 위해 하나의 코어가 여러 개의 스레드를 맡아서 번갈아가면서 한다.

 

 

스레드 스케줄링

스케줄링은 선입선출로 큐로 작업되어있다.

main문에서는 return이 나올 때,

주로 마지막 문장이 끝났을 때 스레드가 종료된다.

 

 

def main():
    for i in range(10):
        print("동작")
        if i == 5:
            return

if __name__ == "__main__":
    main()

for문 한바퀴 도는 데 1초가 걸린다고 하고

같은 main문 6개를 10개의 스레드에 돌리게 하고 동시에 돌리면

총 6초 걸린다.(for문이 6번 돈다고 할때)

스레드의 동시성의 성질은 변하지 않으면서 스케쥴링을 한다.

 

 

싱글코어 멀티코어
코어수가 적어 동시성이 많이 발생
우선순위가 높은 스레드가 실행 기회를 더 많이 가지기 때문에 우선순위가 낮은 스레드보다 계산 작업을 빨리 끝낸다.
최소 5개 이상의 스레드가 실행되어야 우선순위의 영향을 받는다.

 

파이썬도 C처럼 동작하게 하는 것 - C파이썬 이 있다.

 

 

스레드의 일반적인 상태

스레드의 객체가 생성되면 실행 대기 상태와 실행 상태를 계속 반복하다가

메소드 마지막 줄에 다달았을 때 종료하게 된다.

ex) input() 에서 동작

 

sleep()

일정 시간 동안 일시 정지

반드시 초 단위 설정한다.

커서가 그대로 있는 거

코드의 실행시간은 흘러간다.

 

스레드간 협업

서로 기다려주면서 해준다.

스위치가 참이면 종료 / 예외처리도 가능

 

데몬(daemon) 스레드

주 스레드의 작업 돕는 보조적인 역할을 수행하는 스레드

주 스레드가 종료되면 데몬 스레드는 강제적으로 자동 종료

ex) word문서에서 자동적으로 저장되는 기능은 데몬 스레드가 한다.

일반 스레드 개념으로는 모든 스레드가 종료되어야만 비로소 프로세서가 종료될 수 있다.

하지만 데몬 스레드는 주 스레드가 종료가 된다면 자동으로 종료된다.

 

주 스레드를 생성하기 전에 데몬 스레드를 생성해야 한다.(데몬 스레드 만들거야~)

스레드는 한번 생성되면 기능을 수정할 수 없다.


2. 네트워크 추가

1. 기본 개념

데이터 전송 과정: 송신자(process) -> 메시지 -> 수신자(process)

 

전송매체 : 메시지 전달되는 경로

ex) 광케이블, 레이저, 무선파

 

프로토콜 : 데이터 통신을 수행할 때 규칙들의 집합, 규약

송신자와 수신자가 서로 합의가 되어야 통신이 진행된다.

구문 - 무엇을 전송할 것인가

의미 - 어떻게 동작할 것인가

타이밍 - 언제 전송할 것인가

 

프로토콜의 구조

인터페이스 - 정해진 규정 ex) 110v 콘센트 / 

 

 

네트워크 모델은 프로토콜로 정의 된다.

대표적으로 OSI 7계층이 기본 모델이다. - TCP / IP 프로토콜

1 - 응용프로그램 을 연다.

2 - 데이터 암호화, 압축

3 - 네트워크 연결 제어를 대화 단위로 수행

4 - 전송계층 데이터 전송5 - 네트워크 : 데이터 전송 경로 설정6 - 데이터 링크 : 데이터 오류 및 흐름 제어7- 물리 계층 : 데이터를 전기적 신호로 변환

 

1,2,3을 묶어 3개의 계층만 쓴다. (하위 계층은 굳이 언급하지 않는다.)6,7 TCP/IP정보를 받는다.4,5 데이터 전송을 책임진다.1 애플리케이션 계층으로 최종 사용자에게 서비스를 제공한다.

 

 

TCP / IP소규모 네트워크를 사용할 때 쓴다.

 

 

네트워크 인터페이스 계층

 

네트워크 계층 

 

 

 

 

 

2. 소켓

클라이언트의 요청을 서버가 받으면 그제서야 소켓이 만들어진다.

 

https://olivejua-develop.tistory.com/68

 

멀티스레드의 개념

멀티스레드를 설명하기에 앞서 프로세스와 스레드부터 먼저 짚고 넘어가고자 한다. 프로세스와 스레드 프로세스 (Process) 프로세스는 현재 실행 중인 프로그램이다. 프로그램이 실행 중이지 않

olivejua-develop.tistory.com

 

 

 

https://covenant.tistory.com/222

 

[개정판] 어떻게 네트워크를 공부할 것인가?(네트워크 공부 방법에 대하여)

시작하며 제가 전공 신입생 시절 컴퓨터 공학을 어떻게 공부해야 할지 검색을 하면 홍보성 학원 강의만 나왔습니다. 개발자가 되기 위해서 컴퓨터 공학의 기초 과목이 중요하다고 하는데 어떻

covenant.tistory.com

와 이 분 블로그 좋다.

 

서버가 포트 번호를 가지고 있다. 클라이언트는 내가 보낼 서버의 포트 번호를 붙여서 보낸다.

 

socket모듈

asyncio모듈

select 모듈 : 어떤 소켓을 사용할 것인지 선택해준다.

selectors모듈

socketserver모듈 - 클라이언트로부터 요청이 들어오면 서버의 소켓을 만들어주는 모듈

twisted 모듈

 

 

연습1)

보조 스레드가 끝날  때까지 메인 스레드가 끝나도 프로그램이 종료되지 않는다.

import threading,time

# 메소드에 스레드가 할 일을 정리해준다.
# 서브 스레드가 할 일
def f(x, y):
    print("스레드 시작")
    time.sleep(y) # 스레드 잠재우기, y초간 스레드가 멈춘다.
    print(f"{x}를 입력 받았다.")
    return

#메인 스레드가 할 일
def main():
    # 타겟과 입력 매개를 준다.
    t = threading.Thread(target=f, args=("data_x", 10))  # 이름을 꼭 명명해주자
    # 스레드를 꼭 스타트 시켜주자!
    t.start()
    print("main_실행완료")

if __name__ == "__main__":
    main()
    print("main 스레드 종료")

결과

C:\Users\User\AppData\Local\Programs\Python\Python39\python.exe C:/Users/User/Desktop/python_yb/pythonProject/main.py
스레드 시작main_실행완료
main 스레드 종료

data_x를 입력 받았다.

종료 코드 0(으)로 완료된 프로세스

 

 

 

연습2) 데몬 스레드

객체부터 먼저 만들고 옵션 값들을 넣는다.

# 타겟과 입력 매개를 준다.
    t = threading.Thread(target=f)  # 이름을 꼭 명명해주자
    t.daemon = True
    # 스레드를 꼭 스타트 시켜주자!
    t.start()

데몬 스레드를 설정할거면 start() 보다 무조건 먼저 실행되도록 코드를 짜야한다.

메인인 2x10=20초가 경과되면 30000초가 동작되기도 전에 데몬이 끝난다.

import threading,time

# 메소드에 스레드가 할 일을 정리해준다.
# 서브 스레드가 할 일
def f():
    print("스레드 시작")
    for i in range(10000):
        time.sleep(3) # 스레드 잠재우기, y초간 스레드가 멈춘다.
        print("자동저장") # 일정 시간 동안 동작
    print("반복 저장 횟수 초과")
    return

#메인 스레드가 할 일
def main():
    # 타겟과 입력 매개를 준다.
    t = threading.Thread(target=f)  # 이름을 꼭 명명해주자
    t.daemon = True
    # 스레드를 꼭 스타트 시켜주자!
    t.start()
    print("main_실행완료")
    for i in range(10):
        time.sleep(2)


if __name__ == "__main__":
    main()
    print("main 스레드 종료")

결과

스레드 시작main_실행완료

자동저장
자동저장
자동저장
자동저장
자동저장
자동저장
main 스레드 종료

종료 코드 0(으)로 완료된 프로세스

 

10 -> 6으로 변경하면

def main():
    # 타겟과 입력 매개를 준다.
    t = threading.Thread(target=f)  # 이름을 꼭 명명해주자
    t.daemon = True
    # 스레드를 꼭 스타트 시켜주자!
    t.start()
    print("main_실행완료")
    for i in range(6):
        time.sleep(2)

12초간만 데몬 스레드가 동작하고 종료된다.

스레드 시작
main_실행완료
자동저장
자동저장
자동저장
main 스레드 종료자동저장


종료 코드 0(으)로 완료된 프로세스

 

A스레드와 B스레드가 동시에 공유 작업을 썼을 때의 문제점

import threading,time
#lock = threading.Lock()
x = 0
x1 = 0

def f():
    global x
    print("A 스레드 시작")
    x += 1
    time.sleep(3)
    print("A 스레드 동작 후: ",x)
    return

def f_b():
    global x
    print("B 스레드 시작")
    x *= 3
    time.sleep(2)
    print("B 스레드 동작 후: ",x)
    return


#메인 스레드가 할 일
def main():
    global x
    global x1
    
    t_a = threading.Thread(target=f)
    t_b = threading.Thread(target=f_b)
    t_a.start()
    t_b.start()
    print("main_실행완료")

    # A
    for i in range(1):
        x1 += 1
        time.sleep(5)
        print("A의 동작: ",x1)
    # B
    for i in range(1):
        x1 *= 10
        time.sleep(5)
        print("B의 동작: ",x1)


if __name__ == "__main__":
    main()
    print("main 스레드 종료")


A 스레드 시작
B 스레드 시작
main_실행완료
B 스레드 동작 후:  3
A 스레드 동작 후:  3
A의 동작:  1
B의 동작:  10
main 스레드 종료

종료 코드 0(으)로 완료된 프로세스

 

이러한 문제를 막기 위해 sleep 동안 접근 못하게 lock객체를 걸어준다.

import threading,time
lock = threading.Lock()
x = 0
x1 = 0

def f():
    global x
    print("A 스레드 시작")
    lock.acquire()
    x += 1
    time.sleep(3)
    lock.release()
    print("A 스레드 동작 후: ",x)
    return

def f_b():
    global x
    print("B 스레드 시작")
    lock.acquire()
    x *= 3
    time.sleep(2)
    lock.release()
    print("B 스레드 동작 후: ",x)
    return


#메인 스레드가 할 일
def main():
    global x
    global x1

    t_a = threading.Thread(target=f)
    t_b = threading.Thread(target=f_b)
    t_a.start()
    t_b.start()
    print("main_실행완료")

    # A
    for i in range(1):
        x1 += 1
        time.sleep(5)
        print("A의 동작: ",x1)
    # B
    for i in range(1):
        x1 *= 10
        time.sleep(5)
        print("B의 동작: ",x1)


if __name__ == "__main__":
    main()
    print("main 스레드 종료")


A 스레드 시작
B 스레드 시작
main_실행완료
A 스레드 동작 후:  1
B 스레드 동작 후:  3
A의 동작:  1
B의 동작:  10
main 스레드 종료

종료 코드 0(으)로 완료된 프로세스

 

 

같은 스레드로 구분(먼저 실행되는 스레드가 끝나고 다음 작업이 시작된다.)

 

각각 다른 스레드라도 동일한 자원을 다루고 있을 때

 

 

 

 

꼬이게 해보자....(그만해ㅜㅠㅜㅠㅜ 뭔소리야 도대체)

아래 코드는 충돌이 일어난다.

import threading,time
lock = threading.Lock()
x = 0
x1 = 0

def f_a():
    global x,x1
    print("A 스레드 시작")
    time.sleep(5)
    print("5초 후 시작")
    lock.acquire()
    print("acquire 하고 5초 후 시작")
    x += x1
    time.sleep(10)
    lock.release()
    print("A 스레드 동작 후: ",x)
    return

def f_b():
    global x,x1
    print("B 스레드 시작")
    lock.acquire()
    x1 = 20
    time.sleep(10)
    lock.release()
    print("10초 후 시작")

    lock.acquire()
    print("acquire 하고 10초 후 시작")
    x *= 3
    time.sleep(10)
    lock.release()
    print("B 스레드 동작 후: ",x)
    return


#메인 스레드가 할 일
def main():
    global x

    t_a = threading.Thread(target=f_a)
    t_b = threading.Thread(target=f_b)
    t_a.start()
    t_b.start()
    print("main_실행완료")

if __name__ == "__main__":
    main()
    print("main 스레드 종료")


A 스레드 시작
B 스레드 시작
main_실행완료
main 스레드 종료
5초 후 시작
10초 후 시작acquire 하고 5초 후 시작

A 스레드 동작 후: acquire 하고 10초 후 시작
 20
B 스레드 동작 후:  60

종료 코드 0(으)로 완료된 프로세스

A가 시작하자마자 B가 물어버리고

B시작하자마자 A가 물어버리는 관계를 보여준다.

 

 

스레드 하나를 더 늘려서 C 스레드 까지 추가해보자

import threading,time
lock = threading.Lock()
x = 0
x1 = 0

def f_a():
    global x,x1
    print("A 스레드 시작")
    time.sleep(5)
    print("A: 5초 후 시작")
    lock.acquire()
    print("A: acquire 하고 5초 후 시작")
    x += x1
    time.sleep(10)
    lock.release()
    print("A 스레드 동작 후: ",x)
    return

def f_b():
    global x,x1
    print("B 스레드 시작")
    lock.acquire()
    x1 = 20
    time.sleep(10)
    lock.release()
    print("B: 10초 후 시작")

    lock.acquire()
    print("B: acquire 하고 10초 후 시작")
    x *= 3
    time.sleep(10)
    lock.release()
    print("B 스레드 동작 후: ",x)
    return

def f_c():
    time.sleep(3)
    print("c 스레드 종료")


#메인 스레드가 할 일
def main():

    t_a = threading.Thread(target=f_a)
    t_b = threading.Thread(target=f_b)
    t_c = threading.Thread(target=f_c)

    t_a.start()
    t_b.start()
    t_c.start()
    print("main_실행완료")

if __name__ == "__main__":
    main()
    print("main 스레드 종료")


A 스레드 시작
B 스레드 시작
main_실행완료
main 스레드 종료
c 스레드 종료
A: 5초 후 시작
B: 10초 후 시작
A: acquire 하고 5초 후 시작
A 스레드 동작 후: B: acquire 하고 10초 후 시작
 20
B 스레드 동작 후:  60

종료 코드 0(으)로 완료된 프로세스

 


TCP_1, TC_2 코드 두 개 모두 열고 실행해야 한다.

 

data 안에 있는 자료를 꺼낸다. : decode()

서버와 클라이언트가 data를 주고 받고 잘 받았는지 확인하는 과정을 코드로 구현하였다.

에포?? FO?

 

버퍼사이즈 만큼 문자를 받겠다.

msg = sock.recv(1024)

 

 

 

클라이언트가 종료하겠다., 오는 요청이 없을 때 break 걸고 종료되게 된다.

로컬로 잡은 코드

 

server IP: 

PORTEMDEMD

 

message : end 를 하면 프로그램이 종료된다.

인터넷 망으로 연결된 동일한 랜 망에서만 접속 가능하다.


메세지를 무는 경우가 발생

싱글 스레드로 동작하므로 작업을 물게되면 메인 시스템이 처리를 할 수 없다.


멀티 스레드

th_s_2.py

proto : PORT

상속 받는 것??

 같은 TCP이지만 두 스레드가 동작하므로 소켓 연결은 되지만 동작이 안된다.

C는 클라이언트