파이썬에서 스레딩을 어떻게 사용할 수 있습니까?


1279

파이썬의 스레딩을 이해하려고합니다. 설명서와 예제를 살펴 봤지만 솔직히 말하면 많은 예제가 지나치게 정교하여 이해하기가 어렵습니다.

멀티 스레딩으로 분할 된 작업을 어떻게 명확하게 보여줍니까?


31
이 주제에 대한 일반적인 일반적인 논의 는 Jeff Knupp 의 Python 's Hardest Problem 에서 찾을 수 있습니다 . 요약하면 스레딩은 초보자에게는 적합하지 않습니다.
Matthew Walker

112
하하, 스레딩은 모두를위한 것이라고 생각하는 경향이 있지만 초보자는 스레딩을위한 것이 아닙니다 :))))))
Bohdan

42
새로운 언어 기능이 활용 될 때 사람들이 모든 대답을 읽어야한다고 주장하기 만하면됩니다.
Gwyn Evans

5
C로 핵심 로직을 작성하고 ctypes를 통해 호출하여 실제로 파이썬 스레딩을 활용하십시오.
aaa90210

4
난 그냥 것을 추가하고 싶었 PyPubSub이 제어 스레드 흐름에 메시지를 보내고받을 수있는 좋은 방법입니다
ytpillai

답변:


1417

이 질문은 2010 년에 요청 된 이후 mappool을 사용 하여 Python으로 간단한 멀티 스레딩을 수행하는 방법이 실제로 단순화되었습니다 .

아래 코드는 기사 / 블로그 게시물 에서 제공합니다. 한 줄에있는 병렬 처리 : 일일 스레딩 작업을위한 더 나은 모델 . 아래에 요약하겠습니다. 몇 줄의 코드로 끝납니다.

from multiprocessing.dummy import Pool as ThreadPool
pool = ThreadPool(4)
results = pool.map(my_function, my_array)

멀티 스레드 버전은 다음과 같습니다.

results = []
for item in my_array:
    results.append(my_function(item))

기술

맵은 멋진 작은 기능이며, 파이썬 코드에 병렬 처리를 쉽게 주입하는 데 핵심입니다. 익숙하지 않은 사람들에게지도는 Lisp와 같은 기능적 언어에서 해제 된 것입니다. 시퀀스에 다른 함수를 매핑하는 함수입니다.

Map은 시퀀스를 반복 처리하고 함수를 적용하며 모든 결과를 끝에 편리한 목록에 저장합니다.

여기에 이미지 설명을 입력하십시오


이행

병렬 버전의 맵 함수는 두 개의 라이브러리, 즉 멀티 프로세싱과 잘 알려지지는 않았지만 환상적인 단계 자식 인 multiprocessing.dummy에 의해 제공됩니다.

multiprocessing.dummy멀티 프로세싱 모듈과 정확히 동일 하지만 대신 스레드를 사용합니다 ( 중요한 차이점 은 CPU를 많이 사용하는 작업에 여러 프로세스를 사용하고 I / O에 대한 스레드를 사용하는 동안 ).

multiprocessing.dummy는 멀티 프로세싱의 API를 복제하지만 스레딩 모듈을 둘러싸는 래퍼에 지나지 않습니다.

import urllib2
from multiprocessing.dummy import Pool as ThreadPool

urls = [
  'http://www.python.org',
  'http://www.python.org/about/',
  'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html',
  'http://www.python.org/doc/',
  'http://www.python.org/download/',
  'http://www.python.org/getit/',
  'http://www.python.org/community/',
  'https://wiki.python.org/moin/',
]

# Make the Pool of workers
pool = ThreadPool(4)

# Open the URLs in their own threads
# and return the results
results = pool.map(urllib2.urlopen, urls)

# Close the pool and wait for the work to finish
pool.close()
pool.join()

그리고 타이밍 결과 :

Single thread:   14.4 seconds
       4 Pool:   3.1 seconds
       8 Pool:   1.4 seconds
      13 Pool:   1.3 seconds

여러 인수 전달 ( Python 3.3 이상에서만 작동 ) :

여러 배열을 전달하려면

results = pool.starmap(function, zip(list_a, list_b))

또는 상수와 배열을 전달하려면

results = pool.starmap(function, zip(itertools.repeat(constant), list_a))

이전 버전의 Python을 사용하는 경우이 해결 방법을 통해 여러 인수를 전달할 수 있습니다 .

( 유용한 의견 을 주신 user136036 에게 감사합니다 .)


90
투표 결과가 너무나 새로워 졌기 때문에 투표가 부족합니다. 이 답변은 아름답게 작동하며 여기에있는 다른 답변보다 구문을 이해하기 훨씬 쉬운 '지도'기능을 보여줍니다.
유휴

25
스레드가 아닌 프로세스입니까? 멀티 프로세스를 시도하는 것 같습니다! = multithread
AturSams

72
그건 그렇고, 여러분, with Pool(8) as p: p.map( *whatever* )부기 줄을 작성 하고 제거 할 수 있습니다.

11
@BarafuAlbino : 유용합니다 .Python 3.3 이상 에서만 작동 한다는 점에 주목할 가치가 있습니다.
fuglede

9
이 답변을 어떻게 남기고 이것이 I / O 작업에만 유용하다는 것을 언급하지 않습니까? 이것은 대부분의 경우 쓸모없는 단일 스레드에서만 실행되며 실제로 일반적인 방법보다 느립니다.
Frobot

714

간단한 예를 들면 다음과 같습니다. 몇 가지 대체 URL을 시도하고 첫 번째 URL의 내용을 반환하여 응답해야합니다.

import Queue
import threading
import urllib2

# Called by each thread
def get_url(q, url):
    q.put(urllib2.urlopen(url).read())

theurls = ["http://google.com", "http://yahoo.com"]

q = Queue.Queue()

for u in theurls:
    t = threading.Thread(target=get_url, args = (q,u))
    t.daemon = True
    t.start()

s = q.get()
print s

이는 스레딩이 간단한 최적화로 사용되는 경우입니다. 각 서브 스레드는 해당 내용을 큐에 놓기 위해 URL이 해결되고 응답하기를 기다리고 있습니다. 각 스레드는 데몬입니다 (메인 스레드가 종료되는 경우 프로세스를 유지하지 않음). 기본 스레드는 모든 하위 스레드를 시작 get하고 대기열에서 하나를 수행 할 때까지 대기 한 put다음 결과를 내보내고 종료합니다 (데몬 스레드이기 때문에 여전히 실행중인 하위 스레드를 중단합니다).

파이썬에서 스레드의 적절한 사용은 I / O 작업에 항상 연결되어 있습니다 (CPython은 여러 코어를 사용하여 CPU 바인딩 작업을 실행하지 않기 때문에 스레딩의 유일한 이유는 일부 I / O를 기다리는 동안 프로세스를 차단하지 않음) ). 대기열은 거의 항상 작업을 스레드로 팜핑하거나 작업 결과를 수집하는 가장 좋은 방법이며 본질적으로 스레드 안전하므로 잠금, 조건, 이벤트, 세마포 및 기타 인터에 대해 걱정할 필요가 없습니다. 스레드 조정 / 통신 개념.


10
다시 한번 감사드립니다, MartelliBot. 모든 URL이 응답하기를 기다리는 예제를 업데이트했습니다. import Queue, threading, urllib2 q = Queue.Queue () urls = '' ' a.com b.com c.com' ''. split () urls_received = 0 def get_url (q, url) : req = urllib2.Request (url) resp = urllib2.urlopen (req) q.put (resp.read ()) 전역 urls_received urls_received + = 1 url의 u에 대해 urls_received 인쇄 : t = threading.Thread (target = get_url, args = (q, u)) t.daemon = True t.start () q.empty () 및 urls_received <len (urls) : s = q.get () print s
htmldrum

3
@ JRM : 아래의 답변을 보면 스레드가 완료 될 때까지 기다리는 더 좋은 방법은 join()메소드 를 사용 하는 것입니다. 메인 스레드가 끊임없이 프로세서를 소비하지 않고 완료 될 때까지 기다릴 수 있기 때문입니다. 값을 확인하십시오. @ Alex : 감사합니다. 이것은 스레드 사용법을 이해하는 데 정확히 필요한 것입니다.
krs013

6
python3의 경우 'import urllib2'를 'import urllib.request as urllib2'로 바꾸십시오. 인쇄 문에 괄호를 넣습니다.
Harvey

5
파이썬 3의 경우 Queue모듈 이름을로 바꿉니다 queue. 메소드 명은 동일합니다.
JSmyth

2
해결책은 페이지 중 하나만 인쇄한다는 점에 유의하십시오. 대기열에서 두 페이지를 모두 인쇄하려면 다음 명령을 다시 실행하십시오. s = q.get() print s @ krs013 joinQueue.get ()이 차단 되어 있기 때문에 필요하지 않습니다 .
Tom Anderson

256

참고 : 파이썬에서 실제로 병렬화하려면 멀티 프로세싱 모듈을 사용하여 병렬로 실행되는 여러 프로세스를 포크해야합니다 (글로벌 인터프리터 잠금으로 인해 파이썬 스레드는 인터리빙을 제공하지만 실제로는 병렬이 아닌 직렬로 실행되며 단지 I / O 작업을 인터리브 할 때 유용합니다.

그러나 인터리빙 만 찾고 있거나 전역 인터프리터 잠금에도 불구하고 병렬화 할 수있는 I / O 작업을 수행하려는 경우 스레딩 모듈이 시작됩니다. 실제로 간단한 예로, 하위 범위를 병렬로 합산하여 넓은 범위를 합산하는 문제를 고려해 보겠습니다.

import threading

class SummingThread(threading.Thread):
     def __init__(self,low,high):
         super(SummingThread, self).__init__()
         self.low=low
         self.high=high
         self.total=0

     def run(self):
         for i in range(self.low,self.high):
             self.total+=i


thread1 = SummingThread(0,500000)
thread2 = SummingThread(500000,1000000)
thread1.start() # This actually causes the thread to run
thread2.start()
thread1.join()  # This waits until the thread has completed
thread2.join()
# At this point, both threads have completed
result = thread1.total + thread2.total
print result

위의 내용은 I / O가 전혀 없으며 전역 인터프리터 잠금으로 인해 CPython 에서 인터리브 전환 (컨텍스트 전환의 오버 헤드가 추가되었지만) 직렬로 실행되므로 매우 어리석은 예 입니다.


16
@Alex, 나는 그것이 실용적이라고 말하지는 않았지만 OP를 원한다고 생각하는 스레드를 정의하고 생성하는 방법을 보여줍니다.
Michael Aaron Safyan

6
이것은 스레드를 정의하고 생성하는 방법을 보여 주지만 실제로는 하위 범위를 병렬로 합산하지 않습니다. thread1메인 스레드가 차단되는 동안 완료 될 때까지 실행 한 다음와 동일한 일이 발생 thread2하고 메인 스레드가 재개되어 누적 된 값을 인쇄합니다.
martineau

그렇지 super(SummingThread, self).__init__()않습니까? stackoverflow.com/a/2197625/806988
James Andres

@JamesAndres, 아무도 "SummingThread"를 상속받지 않는다고 가정하면 어느 쪽이든 잘 작동합니다. 그러한 경우 super (SummingThread, self)는 MRO (Method Resolution Order)에서 다음 클래스를 조회하는 멋진 방법 일 뿐이며 threading.Thread입니다 ( 두 경우 모두 init 를 호출 함 ). 그러나 super ()를 사용하는 것이 현재 Python에 더 나은 스타일이라는 점에서 맞습니다. Super는 내가이 답변을 제공 할 당시 비교적 최근에 있었으므로 super ()를 사용하지 않고 수퍼 클래스로 직접 호출합니다. 그래도 super를 사용하도록 업데이트하겠습니다.
Michael Aaron Safyan

14
경고 : 이와 같은 작업에는 멀티 스레딩을 사용하지 마십시오! Dave Beazley : dabeaz.com/python/NewGIL.pdf 에서 알 수 있듯이 2 개의 CPU에서 2 개의 python 스레드는 1 개의 CPU에서 1 개의 스레드보다 2 배 더 느린 CPU와 1 개의 CPU에서 2 개의 스레드보다 1.5 배 느린 CPU를 많이 사용합니다. 이 기괴한 동작은 OS와 Python 간의 노력이 잘못 조정 되었기 때문입니다. 스레드의 실제 사용 사례는 I / O 작업입니다. 예를 들어 네트워크를 통한 읽기 / 쓰기를 수행 할 때는 스레드를 넣고 데이터 읽기 / 쓰기를 대기하고 백그라운드로 CPU를 전환하고 데이터를 처리해야하는 다른 스레드로 CPU를 전환하는 것이 좋습니다.
Boris Burkov

98

언급 한 다른 사람들과 마찬가지로 CPython은 GIL 때문에 I / O 대기에만 스레드를 사용할 수 있습니다 .

CPU 바인딩 작업에 여러 코어를 활용하려면 멀티 프로세싱을 사용하십시오 .

from multiprocessing import Process

def f(name):
    print 'hello', name

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

33
이것이 무엇을 설명 할 수 있습니까?
pandita

5
@pandita : 코드가 프로세스를 생성 한 다음 시작합니다. 이제 한 번에 두 가지 일이 발생합니다. 프로그램의 메인 라인과 대상, f기능으로 시작하는 프로세스입니다 . 동시에 메인 프로그램은 프로세스가 종료 join될 때까지 기다립니다 . 주요 부분이 방금 종료 된 경우 서브 프로세스가 완료되거나 완료되지 않을 수 있으므로 join항상 수행하는 것이 좋습니다.
johntellsall

1
map기능 을 포함하는 확장 된 답변은 다음과 같습니다. stackoverflow.com/a/28463266/2327328
philshem

2
@philshem 여기에 언급 된 것처럼 여러분이 게시 한 링크가 스레드 풀 (프로세스 아님)을 사용하고 있음을주의하십시오 . stackoverflow.com/questions/26432411/… 그러나이 답변은 프로세스를 사용하고 있습니다. 나는이 물건에 익숙하지 않지만 (GIL로 인해) 파이썬에서 멀티 스레딩을 사용할 때 특정 상황에서만 성능 향상을 얻는 것처럼 보입니다. 그러나 프로세스 풀을 사용하면 프로세스에서 둘 이상의 코어 작업을 수행하여 멀티 코어 프로세서를 활용할 수 있습니다.
user3731622

3
실제로 유용한 기능을 수행하고 여러 CPU 코어를 활용하는 데 가장 적합한 답변입니다.
Frobot

92

참고 사항 : 스레딩에는 큐가 필요하지 않습니다.

이것은 10 개의 프로세스가 동시에 실행되는 것을 보여줄 수있는 가장 간단한 예입니다.

import threading
from random import randint
from time import sleep


def print_number(number):

    # Sleeps a random 1 to 10 seconds
    rand_int_var = randint(1, 10)
    sleep(rand_int_var)
    print "Thread " + str(number) + " slept for " + str(rand_int_var) + " seconds"

thread_list = []

for i in range(1, 10):

    # Instantiates the thread
    # (i) does not make a sequence, so (i,)
    t = threading.Thread(target=print_number, args=(i,))
    # Sticks the thread in a list so that it remains accessible
    thread_list.append(t)

# Starts threads
for thread in thread_list:
    thread.start()

# This blocks the calling thread until the thread whose join() method is called is terminated.
# From http://docs.python.org/2/library/threading.html#thread-objects
for thread in thread_list:
    thread.join()

# Demonstrates that the main process waited for threads to complete
print "Done"

3
"완료"로 인쇄하려면 마지막 따옴표를 추가하십시오
iChux

1
나는 Martelli보다이 예제를 더 좋아한다. 그러나 printNumber는 다음을 수행하여 진행 상황을 좀 더 명확하게하는 것이 좋습니다. 자기 전에 randint를 변수에 저장해야하며 인쇄는 "Thread"+ str ( number) + ""+ theRandintVariable + "seconds"에 대해 잠자기
Nickolai

각 스레드가 완료된 시점을 알 수있는 방법이 있습니까?
Matt

1
@ 매트 이와 같은 작업을 수행하는 몇 가지 방법이 있지만 필요에 따라 다릅니다. 한 가지 방법은 while 루프에서 감시되고 스레드 끝에서 업데이트되는 싱글 톤 또는 기타 공개적으로 액세스 가능한 변수를 업데이트하는 것입니다.
Douglas Adams

2
두 번째 for루프 가 필요하지 않으므로 thread.start()첫 번째 루프를 호출 할 수 있습니다 .
Mark Mishyn

49

Alex Martelli의 답변이 도움이되었습니다. 그러나 여기에 더 유용하다고 생각되는 수정 된 버전이 있습니다 (적어도 나에게).

업데이트 : Python 2와 Python 3 모두에서 작동

try:
    # For Python 3
    import queue
    from urllib.request import urlopen
except:
    # For Python 2 
    import Queue as queue
    from urllib2 import urlopen

import threading

worker_data = ['http://google.com', 'http://yahoo.com', 'http://bing.com']

# Load up a queue with your data. This will handle locking
q = queue.Queue()
for url in worker_data:
    q.put(url)

# Define a worker function
def worker(url_queue):
    queue_full = True
    while queue_full:
        try:
            # Get your data off the queue, and do some work
            url = url_queue.get(False)
            data = urlopen(url).read()
            print(len(data))

        except queue.Empty:
            queue_full = False

# Create as many threads as you want
thread_count = 5
for i in range(thread_count):
    t = threading.Thread(target=worker, args = (q,))
    t.start()

6
왜 예외를 깰까요?
Stavros Korokithakis

1
개인 취향 만 가능합니다
JimJty

1
코드를 실행하지 않았지만 스레드를 데몬 화하지 않아도됩니까? 마지막 for-loop 후에는 프로그램이 종료 될 수 있다고 생각합니다. 적어도 스레드가 작동해야하기 때문입니다. 더 나은 접근 방식은 작업자 데이터를 대기열에 넣지 않고 출력을 대기열에 넣는 것입니다. 왜냐하면 작업자로부터 대기열로 들어오는 정보를 처리 할뿐만 아니라 스레드도하지 않는 메인 루프를 가질 수 있기 때문입니다 . 그리고 당신 그것이 조기에 종료되지 않는다는 것을 알고 있습니다.
dylnmc

1
@dylnmc, 내 유스 케이스 외부에 있습니다 (입력 큐가 미리 정의되어 있습니다). 당신이 당신의 경로를 가고 싶다면, 나는 셀러리를
JimJty

당신은 내가이 오류를 받고 있어요 이유를 알고 @JimJty : import Queue ModuleNotFoundError: No module named 'Queue'내가 파이썬 3.6.5을 실행하고 일부 게시물은 파이썬 3.6.5에서 그게입니다 언급 queue하지만 난 그것을 변경 후에도 여전히 작동하지 않습니다
user9371654

25

함수가 주어지면 f다음과 같이 스레드하십시오.

import threading
threading.Thread(target=f).start()

인수를 전달하려면 f

threading.Thread(target=f, args=(a,b,c)).start()

이것은 매우 간단합니다. 스레드가 완료되면 스레드가 어떻게 닫히도록 보장합니까?
cameronroytaylor

내가 이해하는 한, 함수가 종료되면 Thread객체가 정리됩니다. 문서를 참조하십시오 . is_alive()필요한 경우 스레드를 확인하는 데 사용할 수 있는 방법이 있습니다.
starfry

나는 보았다 is_alive 방법을, 그러나 나는 스레드에 적용하는 방법을 알아낼 수 없었다. 나는 thread1=threading.Thread(target=f).start()그것을 할당 하고 확인 하려고 thread1.is_alive()했지만 thread1로 채워 None졌으므로 운이 없다. 스레드에 액세스하는 다른 방법이 있는지 알고 있습니까?
cameronroytaylor

4
스레드 객체를 변수에 할당 한 다음 해당 변수를 사용하여 시작해야합니다. thread1=threading.Thread(target=f) 합니다 thread1.start(). 그럼 당신은 할 수 있습니다 thread1.is_alive().
starfry

1
효과가있었습니다. 그렇습니다.thread1.is_alive()False 함수가 종료 되 자마자 리턴 으로 합니다 .
cameronroytaylor

25

나는 이것이 매우 유용하다는 것을 발견했다 : 코어만큼 많은 스레드를 만들고 많은 수의 작업 (이 경우 쉘 프로그램 호출)을 실행하게하십시오.

import Queue
import threading
import multiprocessing
import subprocess

q = Queue.Queue()
for i in range(30): # Put 30 tasks in the queue
    q.put(i)

def worker():
    while True:
        item = q.get()
        # Execute a task: call a shell program and wait until it completes
        subprocess.call("echo " + str(item), shell=True)
        q.task_done()

cpus = multiprocessing.cpu_count() # Detect number of cores
print("Creating %d threads" % cpus)
for i in range(cpus):
     t = threading.Thread(target=worker)
     t.daemon = True
     t.start()

q.join() # Block until all tasks are done

@shavenwarthog는 필요에 따라 "cpus"변수를 조정할 수 있습니다. 어쨌든 하위 프로세스 호출은 하위 프로세스를 생성하고 OS에 의해 CPU에 할당됩니다 (파이썬의 "부모 프로세스"는 하위 프로세스에 대한 "동일한 CPU"를 의미하지 않습니다).
돌고래

2
맞습니다. "스레드가 상위 프로세스와 동일한 CPU에서 시작되었습니다"에 대한 내 의견이 잘못되었습니다. 답장을 보내 주셔서 감사합니다!
johntellsall

1
동일한 메모리 공간을 사용하는 멀티 스레딩과 달리 멀티 프로세싱은 변수 / 데이터를 쉽게 공유 할 수 없습니다. 그래도 +1입니다.
환상적인

22

파이썬 3에는 병렬 작업시작 하는 기능이 있습니다. 이것은 우리의 일을 더 쉽게 만듭니다.

그것은이 스레드 풀링공정 풀링을 .

다음은 통찰력을 제공합니다.

ThreadPoolExecutor 예제 ( source )

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']

# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

ProcessPoolExecutor ( 소스 )

import concurrent.futures
import math

PRIMES = [
    112272535095293,
    112582705942171,
    112272535095293,
    115280095190773,
    115797848077099,
    1099726899285419]

def is_prime(n):
    if n % 2 == 0:
        return False

    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

def main():
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
            print('%d is prime: %s' % (number, prime))

if __name__ == '__main__':
    main()

18

획기적인 새로운 동시 . 퓨처 모듈 사용

def sqr(val):
    import time
    time.sleep(0.1)
    return val * val

def process_result(result):
    print(result)

def process_these_asap(tasks):
    import concurrent.futures

    with concurrent.futures.ProcessPoolExecutor() as executor:
        futures = []
        for task in tasks:
            futures.append(executor.submit(sqr, task))

        for future in concurrent.futures.as_completed(futures):
            process_result(future.result())
        # Or instead of all this just do:
        # results = executor.map(sqr, tasks)
        # list(map(process_result, results))

def main():
    tasks = list(range(10))
    print('Processing {} tasks'.format(len(tasks)))
    process_these_asap(tasks)
    print('Done')
    return 0

if __name__ == '__main__':
    import sys
    sys.exit(main())

executor 접근 방식은 이전에 Java에 익숙하지 않은 모든 사람들에게 친숙하게 보일 수 있습니다.

또한 참고 사항 : 우주를 제정신 상태로 유지하려면 with컨텍스트를 사용하지 않는 경우 풀 / 실행기를 닫는 것을 잊지 마십시오 (매우 훌륭합니다)


17

나에게 스레딩의 완벽한 예는 비동기 이벤트를 모니터링하는 것입니다. 이 코드를보십시오.

# thread_test.py
import threading
import time

class Monitor(threading.Thread):
    def __init__(self, mon):
        threading.Thread.__init__(self)
        self.mon = mon

    def run(self):
        while True:
            if self.mon[0] == 2:
                print "Mon = 2"
                self.mon[0] = 3;

IPython 세션 을 열고 다음과 같은 작업을 수행 하여이 코드를 사용할 수 있습니다 .

>>> from thread_test import Monitor
>>> a = [0]
>>> mon = Monitor(a)
>>> mon.start()
>>> a[0] = 2
Mon = 2
>>>a[0] = 2
Mon = 2

몇 분 기다려

>>> a[0] = 2
Mon = 2

1
AttributeError : 'Monitor'개체에 'stop'특성이 없습니까?
pandita

5
이벤트가 발생하기를 기다리는 동안 CPU 사이클을 폭발시키지 않습니까? 항상 실용적인 방법은 아닙니다.
mogul

3
mogul이 말했듯이, 이것은 지속적으로 실행될 것입니다. 최소한 sleep (0.1)과 같은 짧은 절전 모드를 추가하면 다음과 같은 간단한 예에서 CPU 사용량이 크게 줄어 듭니다.
환상적인

3
이것은 하나의 핵심을 낭비하는 끔찍한 예입니다. 최소한 수면을 추가하십시오. 그러나 적절한 해결책은 몇 가지 신호 메커니즘을 사용하는 것입니다.
PureW

16

대부분의 문서와 튜토리얼은 Python ThreadingQueue모듈을 사용 하므로 초보자에게는 압도적 인 것처럼 보일 수 있습니다.

아마도 concurrent.futures.ThreadPoolExecutor파이썬 3 의 모듈을 고려할 것 입니다.

with절과 목록 이해력과 결합 하면 진정한 매력이 될 수 있습니다.

from concurrent.futures import ThreadPoolExecutor, as_completed

def get_url(url):
    # Your actual program here. Using threading.Lock() if necessary
    return ""

# List of URLs to fetch
urls = ["url1", "url2"]

with ThreadPoolExecutor(max_workers = 5) as executor:

    # Create threads
    futures = {executor.submit(get_url, url) for url in urls}

    # as_completed() gives you the threads once finished
    for f in as_completed(futures):
        # Get the results
        rs = f.result()

15

실제 작업이 수행되지 않은 많은 예제를 보았으며 대부분 CPU에 바인딩되었습니다. 다음은 천만에서 천만 사이의 모든 소수를 계산하는 CPU 바운드 작업의 예입니다. 여기 네 가지 방법을 모두 사용했습니다.

import math
import timeit
import threading
import multiprocessing
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor


def time_stuff(fn):
    """
    Measure time of execution of a function
    """
    def wrapper(*args, **kwargs):
        t0 = timeit.default_timer()
        fn(*args, **kwargs)
        t1 = timeit.default_timer()
        print("{} seconds".format(t1 - t0))
    return wrapper

def find_primes_in(nmin, nmax):
    """
    Compute a list of prime numbers between the given minimum and maximum arguments
    """
    primes = []

    # Loop from minimum to maximum
    for current in range(nmin, nmax + 1):

        # Take the square root of the current number
        sqrt_n = int(math.sqrt(current))
        found = False

        # Check if the any number from 2 to the square root + 1 divides the current numnber under consideration
        for number in range(2, sqrt_n + 1):

            # If divisible we have found a factor, hence this is not a prime number, lets move to the next one
            if current % number == 0:
                found = True
                break

        # If not divisible, add this number to the list of primes that we have found so far
        if not found:
            primes.append(current)

    # I am merely printing the length of the array containing all the primes, but feel free to do what you want
    print(len(primes))

@time_stuff
def sequential_prime_finder(nmin, nmax):
    """
    Use the main process and main thread to compute everything in this case
    """
    find_primes_in(nmin, nmax)

@time_stuff
def threading_prime_finder(nmin, nmax):
    """
    If the minimum is 1000 and the maximum is 2000 and we have four workers,
    1000 - 1250 to worker 1
    1250 - 1500 to worker 2
    1500 - 1750 to worker 3
    1750 - 2000 to worker 4
    so let’s split the minimum and maximum values according to the number of workers
    """
    nrange = nmax - nmin
    threads = []
    for i in range(8):
        start = int(nmin + i * nrange/8)
        end = int(nmin + (i + 1) * nrange/8)

        # Start the thread with the minimum and maximum split up to compute
        # Parallel computation will not work here due to the GIL since this is a CPU-bound task
        t = threading.Thread(target = find_primes_in, args = (start, end))
        threads.append(t)
        t.start()

    # Don’t forget to wait for the threads to finish
    for t in threads:
        t.join()

@time_stuff
def processing_prime_finder(nmin, nmax):
    """
    Split the minimum, maximum interval similar to the threading method above, but use processes this time
    """
    nrange = nmax - nmin
    processes = []
    for i in range(8):
        start = int(nmin + i * nrange/8)
        end = int(nmin + (i + 1) * nrange/8)
        p = multiprocessing.Process(target = find_primes_in, args = (start, end))
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

@time_stuff
def thread_executor_prime_finder(nmin, nmax):
    """
    Split the min max interval similar to the threading method, but use a thread pool executor this time.
    This method is slightly faster than using pure threading as the pools manage threads more efficiently.
    This method is still slow due to the GIL limitations since we are doing a CPU-bound task.
    """
    nrange = nmax - nmin
    with ThreadPoolExecutor(max_workers = 8) as e:
        for i in range(8):
            start = int(nmin + i * nrange/8)
            end = int(nmin + (i + 1) * nrange/8)
            e.submit(find_primes_in, start, end)

@time_stuff
def process_executor_prime_finder(nmin, nmax):
    """
    Split the min max interval similar to the threading method, but use the process pool executor.
    This is the fastest method recorded so far as it manages process efficiently + overcomes GIL limitations.
    RECOMMENDED METHOD FOR CPU-BOUND TASKS
    """
    nrange = nmax - nmin
    with ProcessPoolExecutor(max_workers = 8) as e:
        for i in range(8):
            start = int(nmin + i * nrange/8)
            end = int(nmin + (i + 1) * nrange/8)
            e.submit(find_primes_in, start, end)

def main():
    nmin = int(1e7)
    nmax = int(1.05e7)
    print("Sequential Prime Finder Starting")
    sequential_prime_finder(nmin, nmax)
    print("Threading Prime Finder Starting")
    threading_prime_finder(nmin, nmax)
    print("Processing Prime Finder Starting")
    processing_prime_finder(nmin, nmax)
    print("Thread Executor Prime Finder Starting")
    thread_executor_prime_finder(nmin, nmax)
    print("Process Executor Finder Starting")
    process_executor_prime_finder(nmin, nmax)

main()

Mac OS X 4 코어 컴퓨터의 결과는 다음과 같습니다.

Sequential Prime Finder Starting
9.708213827005238 seconds
Threading Prime Finder Starting
9.81836523200036 seconds
Processing Prime Finder Starting
3.2467174359990167 seconds
Thread Executor Prime Finder Starting
10.228896902000997 seconds
Process Executor Finder Starting
2.656402041000547 seconds

1
어떤 프로세스 실행자는 훨씬 더 나은 CPU 바인딩 작업을 위해 스레딩 초 이상하지 @TheUnfunCat
PirateApp

1
좋은 대답 친구. Windows의 Python 3.6 (적어도)에서 ThreadPoolExecutor는 CPU가 많은 작업에 적합하지 않음을 확인할 수 있습니다. 계산에 코어를 사용하지 않습니다. ProcessPoolExecutor가 생성하는 모든 프로세스에 데이터를 복사하는 반면, 큰 행렬에는 치명적입니다.
아나톨리 알렉 제프

1
매우 유용한 예이지만 작동 방식을 이해하지 못합니다. 우리 if __name__ == '__main__':는 주 호출 이전에 필요합니다 . 그렇지 않으면 측정이 생성되고 인쇄 됩니다. 전에 새 프로세스를 시작하려고 시도했습니다 .
Stein

1
@Stein 나는 그것이 Windows에서만 문제라고 생각합니다.
AMC

12

다음은 스레딩을 사용한 CSV 가져 오기 의 매우 간단한 예입니다 . (라이브러리 포함은 목적에 따라 다를 수 있습니다.)

도우미 기능 :

from threading import Thread
from project import app
import csv


def import_handler(csv_file_name):
    thr = Thread(target=dump_async_csv_data, args=[csv_file_name])
    thr.start()

def dump_async_csv_data(csv_file_name):
    with app.app_context():
        with open(csv_file_name) as File:
            reader = csv.DictReader(File)
            for row in reader:
                # DB operation/query

운전사 기능 :

import_handler(csv_file_name)

9

간단한 예제와 함께이 문제를 직접 해결해야 할 때 유용한 설명을 제공하고자합니다.

이 답변에는 Python의 GIL (전역 인터프리터 잠금) 에 대한 정보 와 multiprocessing.dummy를 사용하여 작성된 간단한 예제 및 간단한 벤치 마크가 있습니다.

글로벌 통역사 잠금 (GIL)

파이썬은 단어의 진정한 의미에서 멀티 스레딩을 허용하지 않습니다. 멀티 스레딩 패키지가 있지만 코드 속도를 높이기 위해 멀티 스레드를 원할 경우 일반적으로 사용하지 않는 것이 좋습니다.

파이썬에는 GIL (Global Interpreter Lock)이라는 구조가 있습니다. GIL은 한 번에 하나의 '스레드'만 실행할 수 있도록합니다. 스레드는 GIL을 획득하고 약간의 작업을 수행 한 다음 GIL을 다음 스레드로 전달합니다.

이것은 사람의 눈에 매우 빠르게 발생하므로 스레드가 병렬로 실행되는 것처럼 보일 수 있지만 실제로는 동일한 CPU 코어를 사용하여 교대로 진행됩니다.

이 모든 GIL 전달은 실행에 오버 헤드를 추가합니다. 즉, 코드를 빠르게 실행하려면 스레딩 패키지를 사용하는 것이 좋지 않습니다.

파이썬의 스레딩 패키지를 사용해야하는 이유가 있습니다. 동시에 몇 가지 작업을 수행하고 효율성이 중요하지 않은 경우 완전히 훌륭하고 편리합니다. 또는 (일부 I / O와 같은) 무언가를 기다려야하는 코드를 실행하는 경우 많은 의미가있을 수 있습니다. 그러나 스레딩 라이브러리에서는 추가 CPU 코어를 사용할 수 없습니다.

멀티 스레딩은 멀티 프로세싱을 통해 운영 체제로 아웃소싱 할 수 있으며 Python 코드를 호출하는 일부 외부 응용 프로그램 (예 : Spark 또는 Hadoop) ) Python 코드가 호출하는 일부 코드 (예 : 파이썬 코드가 비싼 멀티 스레드 작업을 수행하는 C 함수를 호출하도록하십시오).

이것이 중요한 이유

많은 사람들이 GIL이 무엇인지 배우기 전에 멋진 Python 멀티 스레드 코드에서 병목 현상을 찾으려고 많은 시간을 소비하기 때문입니다.

이 정보가 명확 해지면 내 코드는 다음과 같습니다.

#!/bin/python
from multiprocessing.dummy import Pool
from subprocess import PIPE,Popen
import time
import os

# In the variable pool_size we define the "parallelness".
# For CPU-bound tasks, it doesn't make sense to create more Pool processes
# than you have cores to run them on.
#
# On the other hand, if you are using I/O-bound tasks, it may make sense
# to create a quite a few more Pool processes than cores, since the processes
# will probably spend most their time blocked (waiting for I/O to complete).
pool_size = 8

def do_ping(ip):
    if os.name == 'nt':
        print ("Using Windows Ping to " + ip)
        proc = Popen(['ping', ip], stdout=PIPE)
        return proc.communicate()[0]
    else:
        print ("Using Linux / Unix Ping to " + ip)
        proc = Popen(['ping', ip, '-c', '4'], stdout=PIPE)
        return proc.communicate()[0]


os.system('cls' if os.name=='nt' else 'clear')
print ("Running using threads\n")
start_time = time.time()
pool = Pool(pool_size)
website_names = ["www.google.com","www.facebook.com","www.pinterest.com","www.microsoft.com"]
result = {}
for website_name in website_names:
    result[website_name] = pool.apply_async(do_ping, args=(website_name,))
pool.close()
pool.join()
print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))

# Now we do the same without threading, just to compare time
print ("\nRunning NOT using threads\n")
start_time = time.time()
for website_name in website_names:
    do_ping(website_name)
print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))

# Here's one way to print the final output from the threads
output = {}
for key, value in result.items():
    output[key] = value.get()
print ("\nOutput aggregated in a Dictionary:")
print (output)
print ("\n")

print ("\nPretty printed output: ")
for key, value in output.items():
    print (key + "\n")
    print (value)

7

다음은 간단한 예제가있는 멀티 스레딩입니다. 파이썬에서 멀티 스레딩이 어떻게 작동하는지 쉽게 이해할 수 있습니다. 이전 스레드가 작업을 완료 할 때까지 다른 스레드에 대한 액세스를 방지하기 위해 잠금을 사용했습니다. 이 코드 줄을 사용하면

tLock = 스레딩. 경계 세마포어 (값 = 4)

한 번에 여러 프로세스를 허용하고 나중에 또는 이전 프로세스가 완료된 후에 실행될 나머지 스레드를 유지할 수 있습니다.

import threading
import time

#tLock = threading.Lock()
tLock = threading.BoundedSemaphore(value=4)
def timer(name, delay, repeat):
    print  "\r\nTimer: ", name, " Started"
    tLock.acquire()
    print "\r\n", name, " has the acquired the lock"
    while repeat > 0:
        time.sleep(delay)
        print "\r\n", name, ": ", str(time.ctime(time.time()))
        repeat -= 1

    print "\r\n", name, " is releaseing the lock"
    tLock.release()
    print "\r\nTimer: ", name, " Completed"

def Main():
    t1 = threading.Thread(target=timer, args=("Timer1", 2, 5))
    t2 = threading.Thread(target=timer, args=("Timer2", 3, 5))
    t3 = threading.Thread(target=timer, args=("Timer3", 4, 5))
    t4 = threading.Thread(target=timer, args=("Timer4", 5, 5))
    t5 = threading.Thread(target=timer, args=("Timer5", 0.1, 5))

    t1.start()
    t2.start()
    t3.start()
    t4.start()
    t5.start()

    print "\r\nMain Complete"

if __name__ == "__main__":
    Main()

5

이 게시물 에서 빌리면서 우리는 멀티 스레딩, 멀티 프로세싱 및 async /asyncio 와 그 사용법 있습니다.

Python 3 에는 동시성 및 병렬 처리를 위해 새로운 내장 라이브러리가 있습니다. 동시.

따라서 실험을 통해 네 가지 작업 (예 : .sleep()방법)을 다음 Threading-Pool과 같이 실행합니다 .

from concurrent.futures import ThreadPoolExecutor, as_completed
from time import sleep, time

def concurrent(max_worker=1):
    futures = []

    tick = time()
    with ThreadPoolExecutor(max_workers=max_worker) as executor:
        futures.append(executor.submit(sleep, 2))  # Two seconds sleep
        futures.append(executor.submit(sleep, 1))
        futures.append(executor.submit(sleep, 7))
        futures.append(executor.submit(sleep, 3))

        for future in as_completed(futures):
            if future.result() is not None:
                print(future.result())

    print('Total elapsed time by {} workers:'.format(max_worker), time()-tick)

concurrent(5)
concurrent(4)
concurrent(3)
concurrent(2)
concurrent(1)

산출:

Total elapsed time by 5 workers: 7.007831811904907
Total elapsed time by 4 workers: 7.007944107055664
Total elapsed time by 3 workers: 7.003149509429932
Total elapsed time by 2 workers: 8.004627466201782
Total elapsed time by 1 workers: 13.013478994369507

[ 참고 ] :

  • 위의 결과에서 볼 수 있듯이 가장 좋은 경우는 이 4 가지 작업에 대해 3 명의 근로자 였습니다 .
  • 당신은 내가 대신 처리 작업이 있으면 / O 바운드 또는 (차단 multiprocessingthreading당신을 바꿀 수) ThreadPoolExecutorProcessPoolExecutor.

4

이전 솔루션 중 실제로 GNU / Linux 서버 (관리자 권한이없는)에서 여러 코어를 사용한 적이 없습니다. 그들은 방금 단일 코어에서 달렸습니다.

저수준 os.fork인터페이스를 사용하여 여러 프로세스를 생성했습니다. 이것은 나를 위해 일한 코드입니다.

from os import fork

values = ['different', 'values', 'for', 'threads']

for i in range(len(values)):
    p = fork()
    if p == 0:
        my_function(values[i])
        break

2
import threading
import requests

def send():

  r = requests.get('https://www.stackoverlow.com')

thread = []
t = threading.Thread(target=send())
thread.append(t)
t.start()

1
@sP_ 스레드 객체가 있으므로 완료 될 때까지 기다릴 수 있기 때문에 추측하고 있습니다.
Aleksandar Makragić '10

1
t = threading.Thread (target = send ())는 t = threading.Thread (target = send)
이어야합니다

이 답변은 심각한 부정확성을 포함하는 것 외에도 기존 답변을 개선하는 방법에 대한 설명을 제공하지 않기 때문에 하향 조정합니다.
Jules
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.