01- 스레드 및 프로세스, 동시성 및 병렬성(Windows 시스템)

주요 요점:

  • 프로세스는 운영 체제 자원 할당의 기본 단위이고 스레드는 프로세서 작업 스케줄링 및 실행의 기본 단위입니다.

참조 문서: Python3 다중 처리(mutiprocessing)


쓰레드와 프로세스의 차이점

주로 스레드와 프로세스의 차이점과 Python 코드 예제를 소개합니다. 중요한 개념으로 많은 사람들이 스레드와 프로세스가 무엇인지, 그리고 차이점이 무엇인지 종종 혼동합니다.이 기사에서는 스레드와 프로세스에 대해 설명하려고 합니다. 먼저 개념을 살펴보십시오.

1.1 프로세스

메모리에서 실행되는 응용 프로그램입니다. 각 프로세스는 자체 독립 메모리 공간을 가지며 프로세스는 여러 스레드를 가질 수 있습니다.예를 들어 Windows 시스템에서 실행 중인 xx.exe는 프로세스입니다.

영어: process 는 컴퓨터에서 실행 중인 프로그램의 엔터티입니다. 프로세스는 한때 시분할 시스템의 기본 작동 단위였습니다. 프로세스 지향 설계 시스템(예: 초기 UNIX, Linux 2.4 및 이전 버전)에서 프로세스 는 프로그램의 기본 실행 엔터티입니다. 버전) 에서 프로세스 자체는 작업의 기본 단위가 아니라 스레드의 컨테이너입니다. 프로그램 자체는 명령, 데이터 및 해당 구성 형식에 대한 설명일 뿐이며 프로세스는 프로그램(명령 및 데이터)의 실제 실행 인스턴스입니다 .

1.2 스레드

현재 프로세스에서 프로그램 실행을 담당하는 프로세스의 실행 태스크(제어 장치). 프로세스에는 하나 이상의 스레드가 있고 프로세스는 여러 스레드를 실행할 수 있으며 여러 스레드가 데이터를 공유할 수 있습니다.

영어: thread 는 운영 체제가 작업 스케줄링을 수행할 수 있는 가장 작은 단위입니다 . 프로세스에 포함되며 프로세스의 실제 운영 단위입니다. 스레드는 프로세스의 단일 순차적 제어 흐름을 의미하며 여러 스레드가 프로세스에서 동시에 실행될 수 있으며 각 스레드는 서로 다른 작업을 병렬로 수행합니다.

프로그램이 실행되는 엔터티로, 이 문장은 프로그램이 하드디스크에 저장된다는 의미로, 프로그램이 실행되면 여러 프로세스가 생성된다.

프로세스와 달리 같은 종류의 여러 스레드는 프로세스의 힙과 메서드 영역 자원을 공유 하지만 각 스레드는 자체 프로그램 카운터 , 가상 머신 스택로컬 메서드 스택을 가지 므로 시스템에서 스레드를 생성하거나 각 스레드 간에 스레드 작업을 전환할 때 프로세스에 비해 부담이 훨씬 적기 때문에 스레드를 경량 프로세스라고도 합니다.

1.3 프로세스와 스레드의 차이점 요약

스레드는 전통적인 프로세스의 많은 특성을 가지고 있기 때문에 경량 프로세스 또는 프로세스 요소라고도 하며, 기존 프로세스는 스레드가 하나만 있는 작업에 해당하는 중량 프로세스라고 합니다. . 스레드를 도입하는 운영 체제에서 일반적으로 프로세스에는 하나 이상의 스레드를 포함하여 여러 스레드가 있습니다.

근본적인 차이점 : 프로세스는 운영 체제 리소스 할당의 기본 단위인 반면 스레드는 프로세서 작업 예약 및 실행의 기본 단위입니다 .

리소스 오버헤드: 각 프로세스는 독립적인 코드 및 데이터 공간(프로그램 컨텍스트)을 가지며 프로그램 간 전환은 큰 오버헤드를 가지며 스레드는 경량 프로세스로 간주할 수 있으며 동일한 유형의 스레드는 코드 및 데이터 공간을 공유합니다. 독립적인 실행 스택 및 프로그램 카운터(PC) 및 스레드 간 전환 오버헤드가 작습니다.

내포 관계: 프로세스에 여러 스레드가 있는 경우 실행 프로세스는 한 줄이 아니라 여러 줄(스레드)이 함께 완료되며 스레드도 프로세스의 일부이므로 스레드를 경량 프로세스 또는 경량 프로세스 수준 프로세스라고도 합니다.

메모리 할당: 동일한 프로세스의 스레드는 프로세스의 주소 공간과 리소스를 공유하지만 프로세스 간의 주소 공간과 리소스는 서로 독립적입니다.

영향 관계: 프로세스가 충돌한 후 보호 모드의 다른 프로세스에는 영향을 미치지 않지만 스레드가 충돌하면 전체 프로세스가 종료됩니다 . 따라서 멀티프로세싱은 멀티스레딩보다 강력합니다.

실행 프로세스: 각 독립 프로세스에는 프로그램 실행 항목, 순차적 실행 시퀀스 및 프로그램 종료가 있습니다. 그러나 스레드는 독립적으로 실행될 수 없으며 응용 프로그램에 종속되어야 합니다.응용 프로그램은 다중 스레드 실행 제어를 제공하며 둘 다 동시에 실행될 수 있습니다.

'

두 개의 Python 멀티스레딩

2.1 멀티스레딩

다른 언어에서는 CPU가 멀티 코어일 때 여러 스레드의 동시 실행을 지원합니다. 그러나 Python에서는 단일 코어이든 다중 코어이든 동시에 하나의 스레드만 실행할 수 있습니다 . 이것의 근원은 GIL의 존재입니다. GIL의 정식 명칭은 Global Interpreter Lock(Global Interpreter Lock)이며 소스는 Python 설계 초기에 고려하고 데이터 보안을 위해 내린 결정입니다. 스레드가 실행되려면 먼저 GIL을 얻어야 합니다. GIL을 "여권"으로 간주할 수 있으며 Python 프로세스에는 GIL이 하나만 있습니다. 통과할 수 없는 스레드는 실행을 위해 CPU에 들어갈 수 없습니다.

그리고 GIL 잠금 때문에 Python의 프로세스는 동시에 하나의 스레드만 실행할 수 있습니다(GIL을 가져오는 스레드가 실행할 수 있음). 이것이 Python의 멀티 스레딩 효율성이 멀티 코어 CPU에서 높지 않은 근본적인 이유입니다. .

2.2 멀티 스레드 생성

Python은 다중 스레드 작업을 위한 두 가지 모듈, 즉 thread및 를 제공 threading합니다. 전자는 하위 수준 작업을 위한 상대적으로 낮은 수준의 모듈이며 일반적인 응용 프로그램 수준 개발에는 일반적으로 사용되지 않습니다.

  • 방법 1: 직접 사용threading.Thread()
import threading
 
# 这个函数名可随便定义
def run(n):
    print("current task:", n)
 
if __name__ == "__main__":
    t1 = threading.Thread(target=run, args=("thread 1",))
    t2 = threading.Thread(target=run, args=("thread 2",))
    t1.start()
    t2.start()
  • 방법 2: threading.Thread사용자 지정 스레드 클래스에서 상속하고 run메서드를 재정의합니다.
import threading
 
class MyThread(threading.Thread):
    def __init__(self, n):
        super(MyThread, self).__init__()  # 重构run函数必须要写
        self.n = n
 
    def run(self):
        print("current task:", n)
 
if __name__ == "__main__":
    t1 = MyThread("thread 1")
    t2 = MyThread("thread 2")
 
    t1.start()
    t2.start()

2.3 스레드 병합

join함수 실행 순서는 각 스레드를 하나씩 실행하고 실행이 완료된 후 실행을 계속하는 것입니다. 메인 스레드가 종료된 후에도 자식 스레드는 계속 실행 중이며 join이 함수는 종료하기 전에 자식 스레드가 종료될 때까지 메인 스레드를 기다리게 합니다.

import threading
 
def count(n):
    while n > 0:
        n -= 1
 
if __name__ == "__main__":
    t1 = threading.Thread(target=count, args=("100000",))
    t2 = threading.Thread(target=count, args=("100000",))
    t1.start()
    t2.start()
    # 将 t1 和 t2 加入到主线程中
    t1.join()
    t2.join()

2.4 스레드 동기화 및 뮤텍스

스레드 간의 데이터 공유. 여러 스레드가 특정 공유 데이터에서 작동하는 경우 스레드 안전성 문제를 고려해야 합니다. Lock 클래스 는 멀티 스레딩의 경우 데이터의 정확성을 보장하기 위해 뮤텍스 기능을 제공하는 threading모듈에 정의되어 있습니다 .

기본 사용 단계: 취득(), 해제()

#创建锁
mutex = threading.Lock()
#锁定
mutex.acquire([timeout])
#释放
mutex.release()

그 중 lock 메소드는 timeout에 대한 선택적 매개변수 timeout을 가질 수 있습니다. 시간 초과가 설정되면 시간 초과 후 반환 값을 사용하여 잠금을 획득했는지 여부를 확인할 수 있으므로 일부 다른 처리를 수행할 수 있습니다. 구체적인 사용법은 샘플 코드를 참조하십시오.

import threading
import time
 
num = 0
mutex = threading.Lock()
 
class MyThread(threading.Thread):
    def run(self):
        global num 
        time.sleep(1)
 
        if mutex.acquire(1):  
            num = num + 1
            msg = self.name + ': num value is ' + str(num)
            print(msg)
            mutex.release()
 
if __name__ == '__main__':
    for i in range(5):
        t = MyThread()
        t.start()

2.5 타이머

Timer함수가 작업을 수행한 후 몇 초 후에 지정해야 하는 경우 클래스를 사용해야 합니다 . 구체적인 사용법은 다음과 같습니다.

from threading import Timer
 
def show():
    print("Pyhton")
 
# 指定一秒钟之后执行 show 函数
t = Timer(1, hello)
t.start()  

세 가지 Python 다중 프로세스

3.1 다중 프로세스 생성

Python에서 다중 프로세스 작업을 수행하려면 모듈의 클래스와 매우 유사한 클래스인 라이브러리 muiltprocessing사용해야 합니다 . 따라서 코드를 보고 다중 프로세스에 익숙해지십시오.ProcessthreadingThread

  • 방법 1: 직접 사용 Process, 코드는 다음과 같습니다.
from multiprocessing import Process  
 
def show(name):
    print("Process name is " + name)
 
if __name__ == "__main__": 
    proc = Process(target=show, args=('subprocess',))  
    proc.start()  
    proc.join()
  • 방법 2: 사용자 지정 프로세스 클래스에서 상속하고Processrun 메서드를 다시 작성합니다. 코드는 다음과 같습니다.
from multiprocessing import Process
import time
 
class MyProcess(Process):
    def __init__(self, name):
        super(MyProcess, self).__init__()
        self.name = name
 
    def run(self):
        print('process name :' + str(self.name))
        time.sleep(1)
 
if __name__ == '__main__':
    for i in range(3):
        p = MyProcess(i)
        p.start()
    for i in range(3):
        p.join()

3.2 다중 프로세스 통신

프로세스 간에 데이터가 공유되지 않습니다. 프로세스 간 통신이 필요한 경우 Queue 模块또는 를 Pipe 模块달성하십시오.

  • 대기줄

큐는 여러 프로세스 간의 데이터 전송을 실현할 수 있는 다중 프로세스 안전 큐입니다. 그것은 주로 두 가지 기능 putget.

put()은 큐에 데이터를 삽입하는 데 사용되며 put에는 두 개의 선택적 매개변수(blocked 및 timeout)도 있습니다. 차단이 True(기본값)이고 timeout이 양수이면 메서드는 대기열에 공간이 남을 때까지 timeout으로 지정된 시간 동안 차단됩니다. 시간이 초과되면 Queue.Full 예외가 발생합니다. 차단이 False이지만 대기열이 가득 차면 Queue.Full 예외가 즉시 발생합니다.

get()은 queue에서 요소를 읽고 삭제할 수 있습니다 . 마찬가지로 get에는 두 개의 선택적 매개변수(blocked 및 timeout)가 있습니다. 차단이 True(기본값)이고 timeout이 양수 값이면 대기 시간 내에 요소를 가져오지 않고 Queue.Empty 예외가 발생합니다. 차단이 False이면 두 가지 경우가 있는데 Queue에 사용 가능한 값이 있으면 즉시 값을 반환하고, 그렇지 않으면 Queue가 비어 있으면 Queue.Empty 예외가 즉시 발생합니다.

구체적인 사용법은 다음과 같습니다.

from multiprocessing import Process, Queue
 
def put(queue):
    queue.put('Queue 用法')
 
if __name__ == '__main__':
    queue = Queue()
    pro = Process(target=put, args=(queue,))
    pro.start()
    print(queue.get())   
    pro.join()
  • 파이프

Pipe의 본질은 소켓과 같은 데이터 공유가 아닌 프로세스 간 파이프라인 데이터 전송을 사용하는 것입니다. pipe()는 각각 send() 및 recv() 함수가 있는 파이프의 두 끝을 나타내는 두 개의 연결 개체를 반환합니다. 두 프로세스가 동시에 같은 끝에서 읽고 쓰려고 하면 다음과 같이 파이프의 데이터가 손상될 수 있습니다.

from multiprocessing import Process, Pipe
 
def show(conn):
    conn.send('Pipe 用法')
    conn.close()
 
if __name__ == '__main__':
    parent_conn, child_conn = Pipe() 
    pro = Process(target=show, args=(child_conn,))
    pro.start()
    print(parent_conn.recv())   
    pro.join()

3.3 프로세스 풀

여러 프로세스를 생성하기 위해 어리석게 프로세스를 하나씩 생성할 필요가 없습니다. Pool이를 위해 모듈을 사용할 수 있습니다 . 일반적으로 사용되는 Pool 방법은 다음과 같습니다.

구체적인 사용법은 샘플 코드를 참조하십시오.

#coding: utf-8
import multiprocessing
import time
 
def func(msg):
    print("msg:", msg)
    time.sleep(3)
    print("end")
 
if __name__ == "__main__":
    # 维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
    pool = multiprocessing.Pool(processes = 3)
    for i in range(5):
        msg = "hello %d" %(i)
        # 非阻塞式,子进程不影响主进程的执行,会直接运行到 pool.join()
        pool.apply_async(func, (msg, ))   
 
        # 阻塞式,先执行完子进程,再执行主进程
        # pool.apply(func, (msg, ))   
 
    print("Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~")
    # 调用join之前,先调用close函数,否则会出错。
    pool.close()
    # 执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
    pool.join()   
    print("Sub-process(es) done.")
  • 위와 같이 프로세스 풀 Pool이 생성된 후 실제 생성될 프로세스 수가 프로세스 풀의 최대 한도보다 훨씬 크더라도 p.apply_async( test) 코드는 중지 및 대기 없이 계속 실행됩니다. 이는 프로세스 풀이 대기열에 넣을 10개의 요청을 제출하는 것과 동일합니다.
  • p1 = Pool(5) 코드를 실행한 후 5개의 프로세스가 생성되었지만 작업이 할당되지 않았습니다 . 최대 5개의 프로세스를 병렬로 처리합니다 .
  • 풀의 프로세스 작업이 완료되면 프로세스 리소스가 해제되고 풀은 유휴 프로세스에 대한 새로운 요청을 받아 선입 선출 원칙에 따라 실행을 계속합니다.
  • Pool의 모든 프로세스 작업이 완료되면 5개의 좀비 프로세스가 생성되며 메인 스레드가 종료되지 않으면 시스템이 자동으로 리소스를 재활용하지 않으며 재활용을 위해 조인 함수를 호출해야 합니다 .
  • 조인 기능은 주 프로세스가 하위 프로세스가 시스템 자원 회수를 완료할 때까지 대기하는 기능으로, 조인이 없으면 하위 프로세스 종료 여부와 상관없이 주 프로그램이 종료된 후 하위 프로세스가 강제 종료됩니다.
  • Pool 풀을 생성할

3.4 다중 스레드 또는 다중 프로세스 선택

이 문제에서 먼저 프로그램이 어떤 유형의 프로그램에 속하는지 살펴보십시오. 일반적으로 CPU 집약형과 I/O 집약형의 두 가지 유형이 있습니다.

  • CPU 집약적: 이 프로그램은 계산 에 더 중점을 두고 계산을 위해 CPU를 자주 사용해야 합니다. 예를 들어 과학 컴퓨팅 프로그램, 기계 학습 프로그램 등이 있습니다.

  • I/O 집약적: 이름에서 알 수 있듯이 프로그램은 빈번한 입력 및 출력 작업이 필요합니다. 크롤러 프로그램은 전형적인 I/O 집중 프로그램입니다.

프로그램이 CPU를 많이 사용하는 경우 다중 프로세스를 사용하는 것이 좋습니다 . 멀티스레딩은 I/O 집약적인 프로그램 에 더 적합합니다 .

네 가지 동시성 및 병렬성

동시성은 동시에 여러 작업을 처리하고 각 작업이 실행될 기회를 갖도록 이러한 작업 사이를 전환하는 시스템의 기능을 나타냅니다. 동시성은 일반적으로 운영 체제의 프로세스 또는 스레드를 통해 달성됩니다. 동시 시스템에서 각 작업은 일반적으로 자체 실행 프로세스를 가지며 시스템 리소스(예: 메모리 및 CPU 타임 슬라이스)를 공유하므로 여러 작업이 동시에 실행될 때 액세스를 조정하는 방법을 고려해야 합니다. 경쟁 조건 및 교착 상태와 같은 문제를 피하기 위해.

대조적으로, 병렬성은 시스템이 동시에 여러 작업을 실행할 수 있음을 의미합니다. 즉, 여러 작업이 서로 다른 프로세서에서 동시에 실행될 수 있습니다. 병렬 시스템의 주요 목적은 시스템 성능을 향상시키는 것입니다. 다중 프로세서를 사용하면 여러 작업이 서로 간섭하지 않고 동시에 진행되어 처리 시간이 단축됩니다. 동시성과 달리 병렬 시스템에서는 각 작업마다 자체 실행 프로세스와 리소스가 있으며 리소스 공유 및 조정을 고려할 필요가 없습니다.

동시성과 병렬성은 완전히 독립적인 개념이 아니라는 점에 유의해야 합니다. 최신 컴퓨터 시스템에서는 멀티 코어 CPU에서 여러 프로세스 또는 스레드를 실행하는 것과 같이 동시성과 병렬성이 동시에 존재하는 경우가 많습니다.

추천

출처blog.csdn.net/March_A/article/details/130958738