>백엔드 개발 >파이썬 튜토리얼 >Python 스레드 학습 기록 정보

Python 스레드 학습 기록 정보

高洛峰
高洛峰원래의
2017-03-16 15:46:391607검색

소개 및 동기 부여

처리할 데이터가 10,000개 있다고 가정해 보겠습니다. 각 데이터를 처리하는 데는 1초가 걸리지만 각 데이터를 읽는 데는 0.1초밖에 걸리지 않습니다. 데이터는 서로 간섭하지 않습니다. 어떻게 하면 가장 짧은 시간에 실행할 수 있나요?

멀티스레딩(MT)이 등장하기 전 프로그래밍, 컴퓨터 프로그램 실행은 작업 자체가 순차적 실행을 요구하든 전체 프로그램이 여러 하위 작업으로 구성되어 있든 관계없이 호스트의 중앙 처리 장치(CPU)에서 순차적으로 실행되는 실행 시퀀스로 구성됩니다. 이는 하위 작업이 각각 독립적인 경우에도 마찬가지입니다. other(즉, 하나의 하위 작업의 결과는 다른 하위 작업의 결과에 영향을 미치지 않습니다.) 위 문제의 경우 실행 순서를 사용하면 약 10000*0.1 + 10000 = 11000초입니다.

동시에 여러 데이터를 가져올 수 있나요? 가능하다면 작업 효율성이 크게 향상될 수 있습니다.

본질적으로 비동기적인 작업의 경우 각 트랜잭션의 실행 순서는 다음과 같습니다. 불확실하고 무작위이며 예측할 수 없는 문제의 경우 멀티스레딩이 이상적인 솔루션입니다. 이러한 작업은 각 스트림의 여러 실행 스트림으로 나눌 수 있습니다. 완료하려는 목표가 있고 결과가 병합됩니다.

스레드 및 프로세스

프로세스란 무엇인가요?

프로세스(때때로 헤비급 프로세스)는 프로그램의 실행 경로를 기록하는 자체 주소 공간, 메모리, 데이터 스택 및 기타 보조 데이터입니다. 운영 체제는 실행 중인 모든 프로세스를 관리하고 이러한 프로세스에도 공정하게 시간을 할당합니다.

.k 및 생성 작업은 다른 작업을 완료합니다. 그러나 각 프로세스에는 자체 메모리 공간, 데이터 스택 등이 있으므로 IPC(프로세스 간 통신)만 사용할 수 있으며 직접 정보를 공유할 수는 없습니다. . 스레드란 🎜>

스레드(때때로 경량 프로세스라고도 함)는 프로세스와 다소 유사합니다. 차이점은 모든 스레드가 동일한 프로세스에서 실행되고 동일한 실행 환경을 공유한다는 것입니다. "메인 스레드" 스레드

상태

에서 병렬로 실행되는 "미니 프로세스"는 메인 프로세스 또는 프로세스로 상상할 수 있습니다.

스레드는 순차적으로 시작되고 실행되며, 실행 위치를 기록하는 자체 명령 포인터가 있거나 일시적으로 중단될 수 있습니다(휴면이라고도 함). 이를 컨세션(Concession)이라고 하며, 프로세스 내 각 스레드는 동일한 데이터 공간을 공유하므로 프로세스 간보다 더 편리하게 서로 데이터를 공유하고 통신할 수 있습니다. 여러 스레드가 동일한 데이터 조각에 함께 액세스하는 경우 데이터 액세스 순서가 다를 수 있으며 이로 인해 데이터 결과에 불일치가 발생할 수 있습니다. 이를 경쟁 조건이라고 합니다.

스레드는 일반적으로 동시에 실행되지만 단일 CPU 시스템에서는 진정한 동시성이 불가능합니다. 각 스레드는 한 번에 짧은 시간 동안만 실행되고 CPU가 나오도록 예약됩니다. 다른 스레드가 실행되도록 놔두세요. 일부 Python 스레드 학습 기록 정보 함수

는 멀티스레딩에 대한 특별한 수정 없이 완료되기 전에 차단되므로 이 "탐욕스러운" 함수는 CPU 시간 할당을 기울입니다. 결과적으로 각 스레드에 할당된 실행 시간이 동일하지 않을 수 있으며 이는 불공평합니다.

Python, 스레드 및 전역 인터프리터 잠금

GIL(전역 인터프리터 잠금)

먼저 분명히 해야 할 점은 GIL은 Python의 기능이 아니라 구현된 것입니다. Python 파서(CPython)에 의해 도입된 개념입니다. C++가 언어(문법) 표준 세트와 마찬가지로 다른 컴파일러를 사용하여 실행 가능한 코드로 컴파일될 수 있습니다. 동일한 코드 조각은 CPython, PyPy 및 Psyco와 같은 다양한 Python 실행 환경을 통해 실행될 수 있습니다(JPython에는 GIL이 없습니다).

그럼 CPython 구현에서 GIL은 무엇인가요? GIL의 전체 이름은 오해의 소지를 피하기 위해 공식 설명을 살펴보겠습니다.

CPython에서 전역 인터프리터 잠금(GIL)은

이전

이 잠금은

주로

CPython의 메모리 관리가 스레드로부터 안전하지 않기 때문에 필요합니다(그러나 GIL이 존재하기 때문에 다른 기능은 시행하는 보증에 의존

종료
하게 되었습니다.)

Python은 멀티 스레드 프로그래밍을 완벽하게 지원하지만 인터프리터의 C 언어 구현은 전체 병렬 처리로 실행될 때 스레드에 안전하지 않습니다. 실제로 인터프리터는 전역 인터프리터 잠금으로 보호되어 언제든지 하나의 Python 스레드만 실행되도록 보장합니다.

멀티 스레드 환경에서 Python 가상 머신은 다음과 같이 실행됩니다.

  1. GIL 설정

  2. 스위치 스레드로

  3. 을 실행하려면

  • 스레드를 절전 상태로 설정

  • GIL 잠금 해제

  • 위 단계를 다시 반복

  • 모든 I/O 지향( 호출 내장 작업 시스템 C 코드) 프로그램의 경우 GIL은 이 I/O 호출 전에 해제되므로 이 스레드가 I/O를 기다리는 동안 다른 스레드가 실행될 수 있습니다. 스레드가 많은 I/O 작업을 사용하지 않으면 자체 시간 조각 동안 프로세서(및 GIL)를 차지합니다. 즉, I/O 집약적인 Python 프로그램은 컴퓨팅 집약적인 프로그램보다 다중 스레드 환경을 더 잘 활용할 수 있습니다.

    종료 스레드

    스레드는 컴퓨팅을 마치면 종료됩니다. 스레드는 thread.exit()와 같은 종료 함수를 호출할 수 있습니다. 또는 sys.exit()와 같은 프로세스를 종료하는 Python의 표준 메서드를 사용하거나 SystemExit 예외를 던질 수 있습니다. 그러나 스레드를 직접 "종료"할 수는 없습니다.

    Python에서 스레드 사용

    Python은 Win32,

    Linux, Solaris, MacOS, *BSD 등 대부분의 Unix 계열 시스템에서 실행될 때 다중 스레드 프로그래밍을 지원합니다. Python은 pthread라고 알려진 POSIX 호환 스레드를 사용합니다.

    기본적으로 인터프리터에서

    >> import thread
    오류가 보고되지 않으면 스레드를 사용할 수 있습니다.

    Python의 스레딩 모듈

    Python은 스레드, 스레딩 및 대기열을 포함하여 멀티 스레드 프로그래밍을 위한 여러 모듈을 제공합니다. 스레드 및 스레딩 모듈을 사용하면 프로그래머가 스레드를 생성하고 관리할 수 있습니다. 스레드 모듈은 기본 스레드 및 잠금 지원을 제공하는 반면, 스레딩은 더 높은 수준의 보다 강력한 스레드 관리 기능을 제공합니다. 대기열 모듈을 사용하면 사용자는 여러 스레드 간에 데이터를 공유하는 데 사용할 수 있는

    데이터 구조를 생성할 수 있습니다.

    핵심 팁: 스레드 모듈 사용을 피하세요
    다음 고려 사항으로 인해 스레드 모듈 사용을 권장하지 않습니다.

    1. 더 높은 수준의 스레딩 모듈은 더 발전되어 스레드를 더욱 완벽하게 지원하며, 스레드 모듈에서

      속성을 사용하면 스레딩과 충돌할 수 있습니다. 둘째, 하위 수준 스레드 모듈에는 동기화 기본 요소가 거의 없는 반면(실제로는 하나만 있음) 스레딩 모듈에는 많은 동기화 기본 요소가 있습니다.

    2. 메인 스레드가 종료되면 모든 스레드는 경고나 일반적인 정리 없이 강제로 종료됩니다. 이전에 말했듯이, 최소한 스레딩 모듈은 중요한 하위 스레드가 종료된 후 프로세스가 종료되도록 보장할 수 있습니다.

    스레드 모듈

    스레드 모듈은 스레드 생성 외에도 기본 동기화 데이터 구조도 제공합니다. lock

    객체(lock 객체, 기본 잠금, 단순 잠금, 뮤텍스 잠금, 뮤텍스, 이진 세마포라고도 함).

    스레드 모듈 함수

    다음은 스레드 사용 예입니다.

    import thread
    from time import sleep, time
    
    
    def loop(num):
        print('start loop at:', time())
        sleep(num)
        print('loop done at:', time())
    
    
    def loop1(num):
        print('start loop 1 at:', time())
        sleep(num)
        print('loop 1 done at:', time())
    
    
    def main():
        print('starting at:', time())
        thread.start_new_thread(loop, (4,))
        thread.start_new_thread(loop1, (5,))
        sleep(6)
        print('all DONE at:', time())
    
    if name == 'main':
        main()
    
    ('starting at:', 1489387024.886667)
    ('start loop at:', 1489387024.88705)
    ('start loop 1 at:', 1489387024.887277)
    ('loop done at:', 1489387028.888182)
    ('loop 1 done at:', 1489387029.888904)
    ('all DONE at:', 1489387030.889918)
    start_new_thread()에는 처음 두 개의 매개변수가 필요합니다. 따라서 실행하려는 함수가 매개변수를 사용하지 않더라도 여전히 빈 튜플을 전달해야 합니다.

    sleep(6)이라는 문장을 추가하는 이유는 메인 스레드를 중지하지 않으면 메인 스레드가 다음 명령문을 실행하고 "all done"을 표시한 다음 실행 중인 루프를 닫기 때문입니다(). loop1() 및 loop1() 스레드가 종료되었습니다.

    我们有没有更好的办法替换使用sleep() 这种不靠谱的同步方式呢?答案是使用锁,使用了锁,我们就可以在两个线程都退出之后马上退出。

    #! -*- coding: utf-8 -*-
    
    import thread
    from time import sleep, time
    
    loops = [4, 2]
    
    def loop(nloop, nsec, lock):
        print('start loop %s at: %s' % (nloop, time()))
        sleep(nsec)
        print('loop %s done at: %s' % (nloop, time()))
        # 每个线程都会被分配一个事先已经获得的锁,在 sleep()的时间到了之后就释放 相应的锁以通知主线程,这个线程已经结束了。
        lock.release()
    
    
    def main():
        print('starting at:', time())
        locks = []
        nloops = range(len(loops))
    
        for i in nloops:
            # 调用 thread.allocate_lock()函数创建一个锁的列表
            lock = thread.allocate_lock()
            # 分别调用各个锁的 acquire()函数获得, 获得锁表示“把锁锁上”
            lock.acquire()
            locks.append(lock)
    
        for i in nloops:
            # 创建线程,每个线程都用各自的循环号,睡眠时间和锁为参数去调用 loop()函数
            thread.start_new_thread(loop, (i, loops[i], locks[i]))
    
        for i in nloops:
            # 在线程结束的时候,线程要自己去做解锁操作
            # 当前循环只是坐在那一直等(达到暂停主 线程的目的),直到两个锁都被解锁为止才继续运行。
            while locks[i].locked(): pass
    
        print('all DONE at:', time())
    
    if name == 'main':
        main()

    为什么我们不在创建锁的循环里创建线程呢?有以下几个原因:

    1. 我们想到实现线程的同步,所以要让“所有的马同时冲出栅栏”。

    2. 获取锁要花一些时间,如果你的 线程退出得“太快”,可能会导致还没有获得锁,线程就已经结束了的情况。

    threading 模块

    threading 模块不仅提供了 Thread 类,还 供了各 种非常好用的同步机制。

    下面是threading 模块里所有的对象:

    1. Thread: 表示一个线程的执行的对象

    2. Lock: 锁原语对象(跟 thread 模块里的锁对象相同)

    3. RLock: 可重入锁对象。使单线程可以再次获得已经获得了的锁(递归锁定)。

    4. Condition: 条件变量对象能让一个线程停下来,等待其它线程满足了某个“条件”。 如,状态的改变或值的改变。

    5. Event: 通用的条件变量。多个线程可以等待某个事件的发生,在事件发生后, 所有的线程都会被激活。

    6. Semaphore: 为等待锁的线程 供一个类似“等候室”的结构

    7. BoundedSemaphore: 与 Semaphore 类似,只是它不允许超过初始值

    8. Timer: 与 Thread 相似,只是,它要等待一段时间后才开始运行。

    守护线程

    另一个避免使用 thread 模块的原因是,它不支持守护线程。当主线程退出时,所有的子线程不 论它们是否还在工作,都会被强行退出。有时,我们并不期望这种行为,这时,就引入了守护线程 的概念
    threading 模块支持守护线程,它们是这样工作的:守护线程一般是一个等待客户请求的服务器, 如果没有客户 出请求,它就在那等着。如果你设定一个线程为守护线程,就表示你在说这个线程 是不重要的,在进程退出的时候,不用等待这个线程退出。
    如果你的主线程要退出的时候,不用等待那些子线程完成,那就设定这些线程的 daemon 属性。 即,在线程开始(调用 thread.start())之前,调用 setDaemon()函数设定线程的 daemon 标志 (thread.setDaemon(True))就表示这个线程“不重要”
    如果你想要等待子线程完成再退出,那就什么都不用做,或者显式地调用 thread.setDaemon(False)以保证其 daemon 标志为 False。你可以调用 thread.isDaemon()函数来判 断其 daemon 标志的值。新的子线程会继承其父线程的 daemon 标志。整个 Python 会在所有的非守护 线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。

    Thread 类

    Thread类提供了以下方法:

    • run(): 用以表示线程活动的方法。

    • start():启动线程活动。

    • join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。

    • is_alive(): 返回线程是否活动的。

    • name(): 设置/返回线程名。

    • daemon(): 返回/设置线程的 daemon 标志,一定要在调用 start()函数前设置

    用 Thread 类,你可以用多种方法来创建线程。我们在这里介绍三种比较相像的方法。

    • 创建一个Thread的实例,传给它一个函数

    • 创建一个Thread的实例,传给它一个可调用的类对象

    • 从Thread派生出一个子类,创建一个这个子类的实例

    下边是三种不同方式的创建线程的示例:

    #! -*- coding: utf-8 -*-
    
    # 创建一个Thread的实例,传给它一个函数
    
    import threading
    from time import sleep, time
    
    loops = [4, 2]
    
    def loop(nloop, nsec, lock):
        print('start loop %s at: %s' % (nloop, time()))
        sleep(nsec)
        print('loop %s done at: %s' % (nloop, time()))
        # 每个线程都会被分配一个事先已经获得的锁,在 sleep()的时间到了之后就释放 相应的锁以通知主线程,这个线程已经结束了。
    
    
    def main():
        print('starting at:', time())
        threads = []
        nloops = range(len(loops))
    
        for i in nloops:
            t = threading.Thread(target=loop, args=(i, loops[i]))
            threads.append(t)
    
        for i in nloops:
            # start threads
            threads[i].start()
    
        for i in nloops:
            # wait for all
            # join()会等到线程结束,或者在给了 timeout 参数的时候,等到超时为止。
            # 使用 join()看上去 会比使用一个等待锁释放的无限循环清楚一些(这种锁也被称为"spinlock")
            threads[i].join()  # threads to finish
    
        print('all DONE at:', time())
    
    if name == 'main':
        main()

    与传一个函数很相似的另一个方法是在创建线程的时候,传一个可调用的类的实例供线程启动 的时候执行——这是多线程编程的一个更为面向对象的方法。相对于一个或几个函数来说,由于类 对象里可以使用类的强大的功能,可以保存更多的信息,这种方法更为灵活

    #! -*- coding: utf-8 -*-
    
    # 创建一个 Thread 的实例,传给它一个可调用的类对象
    
    from threading import Thread
    from time import sleep, time
    
    
    loops = [4, 2]
    
    
    class ThreadFunc(object):
    
        def init(self, func, args, name=""):
            self.name = name
            self.func = func
            self.args = args
    
        def call(self):
            # 创建新线程的时候,Thread 对象会调用我们的 ThreadFunc 对象,这时会用到一个特殊函数 call()。
            self.func(*self.args)
    
    
    def loop(nloop, nsec):
        print('start loop %s at: %s' % (nloop, time()))
        sleep(nsec)
        print('loop %s done at: %s' % (nloop, time()))
    
    
    def main():
        print('starting at:', time())
        threads = []
        nloops = range(len(loops))
    
        for i in nloops:
            t = Thread(target=ThreadFunc(loop, (i, loops[i]), loop.name))
            threads.append(t)
    
        for i in nloops:
            # start threads
            threads[i].start()
    
        for i in nloops:
            # wait for all
            # join()会等到线程结束,或者在给了 timeout 参数的时候,等到超时为止。
            # 使用 join()看上去 会比使用一个等待锁释放的无限循环清楚一些(这种锁也被称为"spinlock")
            threads[i].join()  # threads to finish
    
        print('all DONE at:', time())
    
    
    if name == 'main':
        main()

    最后一个例子介绍如何子类化 Thread 类,这与上一个例子中的创建一个可调用的类非常像。使 用子类化创建线程(第 29-30 行)使代码看上去更清晰明了。

    #! -*- coding: utf-8 -*-
    
    # 创建一个 Thread 的实例,传给它一个可调用的类对象
    
    from threading import Thread
    from time import sleep, time
    
    
    loops = [4, 2]
    
    
    class MyThread(Thread):
    
        def init(self, func, args, name=""):
            super(MyThread, self).init()
            self.name = name
            self.func = func
            self.args = args
    
        def getResult(self):
            return self.res
    
        def run(self):
            # 创建新线程的时候,Thread 对象会调用我们的 ThreadFunc 对象,这时会用到一个特殊函数 call()。
            print 'starting', self.name, 'at:', time()
            self.res = self.func(*self.args)
            print self.name, 'finished at:', time()
    
    
    
    def loop(nloop, nsec):
        print('start loop %s at: %s' % (nloop, time()))
        sleep(nsec)
        print('loop %s done at: %s' % (nloop, time()))
    
    
    def main():
        print('starting at:', time())
        threads = []
        nloops = range(len(loops))
    
        for i in nloops:
            t = MyThread(loop, (i, loops[i]), loop.name)
            threads.append(t)
    
        for i in nloops:
            # start threads
            threads[i].start()
    
        for i in nloops:
            # wait for all
            # join()会等到线程结束,或者在给了 timeout 参数的时候,等到超时为止。
            # 使用 join()看上去 会比使用一个等待锁释放的无限循环清楚一些(这种锁也被称为"spinlock")
            threads[i].join()  # threads to finish
    
        print('all DONE at:', time())
    
    
    if name == 'main':
        main()

    除了各种同步对象和线程对象外,threading 模块还 供了一些函数。

    • active_count(): 当前活动的线程对象的数量

    • current_thread(): 返回当前线程对象

    • enumerate(): 返回当前活动线程的列表

    • settrace(func): 为所有线程设置一个跟踪函数

    • setprofile(func): 为所有线程设置一个 profile 函数

    Lock & RLock

    原语锁定是一个同步原语,状态是锁定或未锁定。两个方法acquire()和release() 用于加锁和释放锁。
    RLock 可重入锁是一个类似于Lock对象的同步原语,但同一个线程可以多次调用。

    Lock 不支持递归加锁,也就是说即便在同 线程中,也必须等待锁释放。通常建议改  RLock, 它会处理 "owning thread" 和 "recursion level" 状态,对于同 线程的多次请求锁 为,只累加
    计数器。每次调 release() 将递减该计数器,直到 0 时释放锁,因此 acquire() 和 release() 必须 要成对出现。

    from time import sleep
    from threading import current_thread, Thread
    
    lock = Rlock()
    
    def show():
        with lock:
            print current_thread().name, i
            sleep(0.1)
    
    def test():
        with lock:
            for i in range(3):
                show(i)
    
    for i in range(2):
        Thread(target=test).start()

    Event

    事件用于在线程间通信。一个线程发出一个信号,其他一个或多个线程等待。
    Event 通过通过 个内部标记来协调多线程运 。 法 wait() 阻塞线程执 ,直到标记为 True。 set() 将标记设为 True,clear() 更改标记为 False。isSet() 用于判断标记状态。

    from threading import Event
    
    def test_event():
        e = Event()
        def test():
            for i in range(5):
                print 'start wait'
                e.wait()
                e.clear()  # 如果不调用clear(),那么标记一直为 True,wait()就不会发生阻塞行为
                print i
    Thread(target=test).start()
    return e
    
    
    e = test_event()

    Condition

    条件变量和 Lock 参数一样,也是一个,也是一个同步原语,当需要线程关注特定的状态变化或事件的发生时使用这个锁定。

    可以认为,除了Lock带有的锁定池外,Condition还包含一个等待池,池中的线程处于状态图中的等待阻塞状态,直到另一个线程调用notify()/notifyAll()通知;得到通知后线程进入锁定池等待锁定。

    构造方法
    Condition([lock/rlock])

    Condition 有以下这些方法:

    • acquire([timeout])/release(): 调用关联的锁的相应方法。

    • wait([timeout]): 调用这个方法将使线程进入Condition的等待池等待通知,并释放锁。使用前线程必须已获得锁定,否则将抛出异常

    • notify(): 调用这个方法将从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()尝试获得锁定(进入锁定池);其他线程仍然在等待池中。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。

    • notifyAll(): 调用这个方法将通知等待池中所有的线程,这些线程都将进入锁定池尝试获得锁定。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。

    from threading import Condition, current_thread, Thread
    
    con = Condition()
    
    def tc1():
        with con:
            for i in range(5):
                print current_thread().name, i
                sleep(0.3)
                if i == 3:
                    con.wait()
    
    
    def tc2():
        with con:
            for i in range(5):
                print current_thread().name, i
                sleep(0.1)
                con.notify()
    
    Thread(target=tc1).start()
    Thread(target=tc2).start()
    
    Thread-1 0
    Thread-1 1
    Thread-1 2
    Thread-1 3    # 让出锁
    Thread-2 0
    Thread-2 1
    Thread-2 2
    Thread-2 3
    Thread-2 4
    Thread-1 4    # 重新获取锁,继续执

    只有获取锁的线程才能调用 wait() 和 notify(),因此必须在锁释放前调用。
    当 wait() 释放锁后,其他线程也可进入 wait 状态。notifyAll() 激活所有等待线程,让它们去抢锁然后完成后续执行。

    生产者-消费者问题和 Queue 模块

    现在我们用一个经典的(生产者消费者)例子来介绍一下 Queue模块。

    生产者消费者的场景是: 生产者生产货物,然后把货物放到一个队列之类的数据结构中,生产货物所要花费的时间无法预先确定。消费者消耗生产者生产的货物的时间也是不确定的。

    常用的 Queue 模块的属性:

    • queue(size): 创建一个大小为size的Queue对象。

    • qsize(): 返回队列的大小(由于在返回的时候,队列可能会被其它线程修改,所以这个值是近似值)

    • empty(): 如果队列为空返回 True,否则返回 False

    • full(): 如果队列已满返回 True,否则返回 False

    • put(item,block=0): 把item放到队列中,如果给了block(不为0),函数会一直阻塞到队列中有空间为止

    • get(block=0): 从队列中取一个对象,如果给了 block(不为 0),函数会一直阻塞到队列中有对象为止

    Queue 模块可以用来进行线程间通讯,让各个线程之间共享数据。

    现在,我们创建一个队列,让 生产者(线程)把新生产的货物放进去供消费者(线程)使用。

    #! -*- coding: utf-8 -*-
    
    from Queue import Queue
    from random import randint
    from time import sleep, time
    from threading import Thread
    
    
    class MyThread(Thread):
    
        def init(self, func, args, name=""):
            super(MyThread, self).init()
            self.name = name
            self.func = func
            self.args = args
    
        def getResult(self):
            return self.res
    
        def run(self):
            # 创建新线程的时候,Thread 对象会调用我们的 ThreadFunc 对象,这时会用到一个特殊函数 call()。
            print 'starting', self.name, 'at:', time()
            self.res = self.func(*self.args)
            print self.name, 'finished at:', time()
    
    
    # writeQ()和 readQ()函数分别用来把对象放入队列和消耗队列中的一个对象。在这里我们使用 字符串'xxx'来表示队列中的对象。
    def writeQ(queue):
        print 'producing object for Q...'
        queue.put('xxx', 1)
        print "size now", queue.qsize()
    
    
    def readQ(queue):
        queue.get(1)
        print("consumed object from Q... size now", queue.qsize())
    
    
    def writer(queue, loops):
        # writer()函数只做一件事,就是一次往队列中放入一个对象,等待一会,然后再做同样的事
        for i in range(loops):
            writeQ(queue)
            sleep(1)
    
    
    def reader(queue, loops):
        # reader()函数只做一件事,就是一次从队列中取出一个对象,等待一会,然后再做同样的事
        for i in range(loops):
            readQ(queue)
            sleep(randint(2, 5))
    
    
    # 设置有多少个线程要被运行
    funcs = [writer, reader]
    nfuncs = range(len(funcs))
    
    
    def main():
        nloops = randint(10, 20)
        q = Queue(32)
        threads = []
    
        for i in nfuncs:
            t = MyThread(funcs[i], (q, nloops), funcs[i].name)
            threads.append(t)
    
        for i in nfuncs:
            threads[i].start()
    
        for i in nfuncs:
            threads[i].join()
            print threads[i].getResult()
    
        print 'all DONE'
    
    
    if name == 'main':
        main()

    FAQ

    进程与线程。线程与进程的区别是什么?

    进程(有时被称为重量级进程)是程序的一次 执行。每个进程都有自己的地址空间,内存,数据栈以及其它记录其运行轨迹的辅助数据。
    线程(有时被称为轻量级进程)跟进程有些相似,不同的是,所有的线程运行在同一个进程中, 共享相同的运行环境。它们可以想像成是在主进程或“主线程”中并行运行的“迷你进程”。

    这篇文章很好的解释了 线程和进程的区别,推荐阅读: http://www.ruanyifeng.com/blo...

    Python 的线程。在 Python 中,哪一种多线程的程序表现得更好,I/O 密集型的还是计算 密集型的?

    由于GIL的缘故,对所有面向 I/O 的(会调用内建的操作系统 C 代码的)程序来说,GIL 会在这个 I/O 调用之 前被释放,以允许其它的线程在这个线程等待 I/O 的时候运行。如果某线程并未使用很多 I/O 操作, 它会在自己的时间片内一直占用处理器(和 GIL)。也就是说,I/O 密集型的 Python 程序比计算密集 型的程序更能充分利用多线程环境的好处。

    线程。你认为,多 CPU 的系统与一般的系统有什么大的不同?多线程的程序在这种系统上的表现会怎么样?

    Python的线程就是C语言的一个pthread,并通过操作系统调度算法进行调度(例如linux是CFS)。为了让各个线程能够平均利用CPU时间,python会计算当前已执行的微代码数量,达到一定阈值后就强制释放GIL。而这时也会触发一次操作系统的线程调度(当然是否真正进行上下文切换由操作系统自主决定)。
    伪代码

    while True:
        acquire GIL
        for i in 1000:
            do something
        release GIL
        /* Give Operating System a chance to do thread scheduling */

    这种模式在只有一个CPU核心的情况下毫无问题。任何一个线程被唤起时都能成功获得到GIL(因为只有释放了GIL才会引发线程调度)。
    但当CPU有多个核心的时候,问题就来了。从伪代码可以看到,从release GIL到acquire GIL之间几乎是没有间隙的。所以当其他在其他核心上的线程被唤醒时,大部分情况下主线程已经又再一次获取到GIL了。这个时候被唤醒执行的线程只能白白的浪费CPU时间,看着另一个线程拿着GIL欢快的执行着。然后达到切换时间后进入待调度状态,再被唤醒,再等待,以此往复恶性循环。
    简单的总结下就是:Python的多线程在多核CPU上,只对于IO密集型计算产生正面效果;而当有至少有一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降。

    线程池。修改 生成者消费者 的代码,不再是一个生产者和一个消费者,而是可以有任意个 消费者线程(一个线程池),每个线程可以在任意时刻处理或消耗任意多个产品。

    위 내용은 Python 스레드 학습 기록 정보의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.