精华内容
下载资源
问答
  • Python多线程详解

    2021-01-27 15:46:37
    线程有5种状态,状态转换的过程如下图所示:多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。考虑这样一种情况:一个列表里所有元素都是0,线程...
  • python中的多线程是一... python多线程详解 什么是线程? 线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。 线程自己不拥有系统资源,只拥有一点儿在运行
  • python多线程详解

    2020-11-26 03:12:18
    python多线程详解一、线程介绍什么是线程线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可...

    python多线程详解

    一、线程介绍

    什么是线程

    线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

    为什么要使用多线程

    线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄和其他进程应有的状态。

    因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

    线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性多个线程共享同一个进程的虚拟空间。线程共享的环境包括进程代码段、进程的公有数据等,利用这些共享的数据,线程之间很容易实现通信。

    操作系统在创建进程时,必须为该进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程来实现并发比使用多进程的性能要高得多。

    总结起来,使用多线程编程具有如下几个优点:进程之间不能共享内存,但线程之间共享内存非常容易。

    操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此,使用多线程来实现多任务并发执行比使用多进程的效率高。

    Python 语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了 Python 的多线程编程。

    二、线程实现

    threading模块

    普通创建方式

    import threading

    import time

    def run(n):

    print("task", n)

    time.sleep(1)

    print('2s')

    time.sleep(1)

    print('1s')

    time.sleep(1)

    print('0s')

    time.sleep(1)

    if __name__ == '__main__':

    t1 = threading.Thread(target=run, args=("t1",))

    t2 = threading.Thread(target=run, args=("t2",))

    t1.start()

    t2.start()

    ----------------------------------

    >>> task t1

    >>> task t2

    >>> 2s

    >>> 2s

    >>> 1s

    >>> 1s

    >>> 0s

    >>> 0s

    自定义线程

    继承threading.Thread来自定义线程类,其本质是重构Thread类中的run方法

    import threading

    import time

    class MyThread(threading.Thread):

    def __init__(self, n):

    super(MyThread, self).__init__() # 重构run函数必须要写

    self.n = n

    def run(self):

    print("task", self.n)

    time.sleep(1)

    print('2s')

    time.sleep(1)

    print('1s')

    time.sleep(1)

    print('0s')

    time.sleep(1)

    if __name__ == "__main__":

    t1 = MyThread("t1")

    t2 = MyThread("t2")

    t1.start()

    t2.start()

    ----------------------------------

    >>> task t1

    >>> task t2

    >>> 2s

    >>> 2s

    >>> 1s

    >>> 1s

    >>> 0s

    >>> 0s

    守护线程

    我们看下面这个例子,这里使用setDaemon(True)把所有的子线程都变成了主线程的守护线程,因此当主进程结束后,子线程也会随之结束。所以当主线程结束后,整个程序就退出了。

    import threading

    import time

    def run(n):

    print("task", n)

    time.sleep(1) #此时子线程停1s

    print('3')

    time.sleep(1)

    print('2')

    time.sleep(1)

    print('1')

    if __name__ == '__main__':

    t = threading.Thread(target=run, args=("t1",))

    t.setDaemon(True) #把子进程设置为守护线程,必须在start()之前设置

    t.start()

    print("end")

    ----------------------------------

    >>> task t1

    >>> end

    我们可以发现,设置守护线程之后,当主线程结束时,子线程也将立即结束,不再执行。

    主线程等待子线程结束

    为了让守护线程执行结束之后,主线程再结束,我们可以使用join方法,让主线程等待子线程执行。

    import threading

    import time

    def run(n):

    print("task", n)

    time.sleep(1) #此时子线程停1s

    print('3')

    time.sleep(1)

    print('2')

    time.sleep(1)

    print('1')

    if __name__ == '__main__':

    t = threading.Thread(target=run, args=("t1",))

    t.setDaemon(True) #把子进程设置为守护线程,必须在start()之前设置

    t.start()

    t.join() # 设置主线程等待子线程结束

    print("end")

    ----------------------------------

    >>> task t1

    >>> 3

    >>> 2

    >>> 1

    >>> end

    多线程共享全局变量

    线程是进程的执行单元,进程是系统分配资源的最小单位,所以在同一个进程中的多线程是共享资源的。

    import threading

    import time

    g_num = 100

    def work1():

    global g_num

    for i in range(3):

    g_num += 1

    print("in work1 g_num is :%d" % g_num)

    def work2():

    global g_num

    print("in work2 g_num is :%d" % g_num)

    if __name__ == '__main__':

    t1 = threading.Thread(target=work1)

    t1.start()

    time.sleep(1)

    t2 = threading.Thread(target=work2)

    t2.start()

    ----------------------------------

    >>> in work1 g_num is : 103

    >>> in work2 g_num is : 103

    互斥锁

    由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁,即同一时刻允许一个线程执行操作。线程锁用于锁定资源,你可以定义多个锁, 像下面的代码, 当你需要独占某一资源时,任何一个锁都可以锁这个资源,就好比你用不同的锁都可以把相同的一个门锁住是一个道理。

    由于线程之间是进行随机调度,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们也称此为“线程不安全”。

    为了方式上面情况的发生,就出现了互斥锁(Lock)

    from threading import Thread,Lock

    import os,time

    def work():

    global n

    lock.acquire()

    temp=n

    time.sleep(0.1)

    n=temp-1

    lock.release()

    if __name__ == '__main__':

    lock=Lock()

    n=100

    l=[]

    for i in range(100):

    p=Thread(target=work)

    l.append(p)

    p.start()

    for p in l:

    p.join()

    递归锁

    RLcok类的用法和Lock类一模一样,但它支持嵌套,,在多个锁没有释放的时候一般会使用使用RLcok类。

    import threading

    import time

    def Func(lock):

    global gl_num

    lock.acquire()

    gl_num += 1

    time.sleep(1)

    print(gl_num)

    lock.release()

    if __name__ == '__main__':

    gl_num = 0

    lock = threading.RLock()

    for i in range(10):

    t = threading.Thread(target=Func, args=(lock,))

    t.start()

    信号量(BoundedSemaphore类)

    互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

    import threading

    import time

    def run(n, semaphore):

    semaphore.acquire() #加锁

    time.sleep(1)

    print("run the thread:%s\n" % n)

    semaphore.release() #释放

    if __name__ == '__main__':

    num = 0

    semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行

    for i in range(22):

    t = threading.Thread(target=run, args=("t-%s" % i, semaphore))

    t.start()

    while threading.active_count() != 1:

    pass # print threading.active_count()

    else:

    print('-----all threads done-----')

    事件(Event类)

    python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下几个方法:clear 将flag设置为“False”

    set 将flag设置为“True”

    is_set 判断是否设置了flag

    wait 会一直监听flag,如果没有检测到flag就一直处于阻塞状态

    事件处理的机制:全局定义了一个“Flag”,当flag值为“False”,那么event.wait()就会阻塞,当flag值为“True”,那么event.wait()便不再阻塞。

    #利用Event类模拟红绿灯

    import threading

    import time

    event = threading.Event()

    def lighter():

    count = 0

    event.set() #初始值为绿灯

    while True:

    if 5 < count <=10 :

    event.clear() # 红灯,清除标志位

    print("\33[41;1mred light is on...\033[0m")

    elif count > 10:

    event.set() # 绿灯,设置标志位

    count = 0

    else:

    print("\33[42;1mgreen light is on...\033[0m")

    time.sleep(1)

    count += 1

    def car(name):

    while True:

    if event.is_set(): #判断是否设置了标志位

    print("[%s] running..."%name)

    time.sleep(1)

    else:

    print("[%s] sees red light,waiting..."%name)

    event.wait()

    print("[%s] green light is on,start going..."%name)

    light = threading.Thread(target=lighter,)

    light.start()

    car = threading.Thread(target=car,args=("MINI",))

    car.start()

    三、GIL(Global Interpreter Lock)全局解释器锁

    在非python环境中,单核情况下,同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在python中,无论有多少核,同时只能执行一个线程。究其原因,这就是由于GIL的存在导致的。

    GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操作cpu,只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的。

    Python多线程的工作过程: python在使用多线程的时候,调用的是c语言的原生线程。拿到公共数据

    申请gil

    python解释器调用os原生线程

    os操作cpu执行运算

    当该线程执行时间到后,无论运算是否已经执行完,gil都被要求释放

    进而由其他进程重复上面的过程

    等其他进程执行完后,又会切换到之前的线程(从他记录的上下文继续执行),整个过程是每个线程执行自己的运算,当执行时间到就进行切换(context switch)。

    python针对不同类型的代码执行效率也是不同的:

    1、CPU密集型代码(各种循环处理、计算等等),在这种情况下,由于计算工作多,ticks计数很快就会达到阈值,然后触发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。 2、IO密集型代码(文件处理、网络爬虫等涉及文件读写的操作),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好。

    使用建议?

    python下想要充分利用多核CPU,就用多进程。因为每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,在python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)。

    GIL在python中的版本差异:

    1、在python2.x里,GIL的释放逻辑是当前线程遇见IO操作或者ticks计数达到100时进行释放。(ticks可以看作是python自身的一个计数器,专门做用于GIL,每次释放后归零,这个计数可以通过sys.setcheckinterval 来调整)。而每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。并且由于GIL锁存在,python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为什么在多核CPU上,python的多线程效率并不高。 2、在python3.x中,GIL不使用ticks计数,改为使用计时器(执行时间达到阈值后,当前线程释放GIL),这样对CPU密集型程序更加友好,但依然没有解决GIL导致的同一时间只能执行一个线程的问题,所以效率依然不尽如人意。

    展开全文
  • python中的多线程是一个非常...import threadingfrom threading import Lock,Threadimport time,os'''python多线程详解什么是线程?线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之...

    166a433ef174f58c09606759cc48862d.png

    python中的多线程是一个非常重要的知识点,今天为大家对多线程进行详细的说明,代码中的注释有多线程的知识点还有测试用的实例。

    码字不易,阅读或复制完了,点个赞!

    import threading

    from threading import Lock,Thread

    import time,os

    '''

    python多线程详解

    什么是线程?

    线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。

    线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所

    拥有的全部资源。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行

    '''

    '''

    为什么要使用多线程?

    线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄

    和其他进程应有的状态。

    因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程之中拥有独立的内存单元,而多个线程共享

    内存,从而极大的提升了程序的运行效率。

    线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程共享一个进程的虚拟空间。线程的共享环境

    包括进程代码段、进程的共有数据等,利用这些共享的数据,线程之间很容易实现通信。

    操作系统在创建进程时,必须为改进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程

    来实现并发比使用多进程的性能高得要多。

    '''

    '''

    总结起来,使用多线程编程具有如下几个优点:

    进程之间不能共享内存,但线程之间共享内存非常容易。

    操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此使用多线程来实现多任务并发执行比使用多进程的效率高

    python语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了python的多线程编程。

    '''

    '''

    普通创建方式

    '''

    # def run(n):

    # print('task',n)

    # time.sleep(1)

    # print('2s')

    # time.sleep(1)

    # print('1s')

    # time.sleep(1)

    # print('0s')

    # time.sleep(1)

    #

    # if __name__ == '__main__':

    # t1 = threading.Thread(target=run,args=('t1',)) # target是要执行的函数名(不是函数),args是函数对应的参数,以元组的形式存在

    # t2 = threading.Thread(target=run,args=('t2',))

    # t1.start()

    # t2.start()

    '''

    自定义线程:继承threading.Thread来定义线程类,其本质是重构Thread类中的run方法

    '''

    # class MyThread(threading.Thread):

    # def __init__(self,n):

    # super(MyThread,self).__init__() #重构run函数必须写

    # self.n = n

    #

    # def run(self):

    # print('task',self.n)

    # time.sleep(1)

    # print('2s')

    # time.sleep(1)

    # print('1s')

    # time.sleep(1)

    # print('0s')

    # time.sleep(1)

    #

    # if __name__ == '__main__':

    # t1 = MyThread('t1')

    # t2 = MyThread('t2')

    # t1.start()

    # t2.start()

    '''

    守护线程

    下面这个例子,这里使用setDaemon(True)把所有的子线程都变成了主线程的守护线程,

    因此当主线程结束后,子线程也会随之结束,所以当主线程结束后,整个程序就退出了。

    所谓’线程守护’,就是主线程不管该线程的执行情况,只要是其他子线程结束且主线程执行完毕,主线程都会关闭。也就是说:主线程不等待该守护线程的执行完再去关闭。

    '''

    # def run(n):

    # print('task',n)

    # time.sleep(1)

    # print('3s')

    # time.sleep(1)

    # print('2s')

    # time.sleep(1)

    # print('1s')

    #

    # if __name__ == '__main__':

    # t=threading.Thread(target=run,args=('t1',))

    # t.setDaemon(True)

    # t.start()

    # print('end')

    '''

    通过执行结果可以看出,设置守护线程之后,当主线程结束时,子线程也将立即结束,不再执行

    '''

    '''

    主线程等待子线程结束

    为了让守护线程执行结束之后,主线程再结束,我们可以使用join方法,让主线程等待子线程执行

    '''

    # def run(n):

    # print('task',n)

    # time.sleep(2)

    # print('5s')

    # time.sleep(2)

    # print('3s')

    # time.sleep(2)

    # print('1s')

    # if __name__ == '__main__':

    # t=threading.Thread(target=run,args=('t1',))

    # t.setDaemon(True) #把子线程设置为守护线程,必须在start()之前设置

    # t.start()

    # t.join() #设置主线程等待子线程结束

    # print('end')

    '''

    多线程共享全局变量

    线程时进程的执行单元,进程时系统分配资源的最小执行单位,所以在同一个进程中的多线程是共享资源的

    '''

    # g_num = 100

    # def work1():

    # global g_num

    # for i in range(3):

    # g_num+=1

    # print('in work1 g_num is : %d' % g_num)

    #

    # def work2():

    # global g_num

    # print('in work2 g_num is : %d' % g_num)

    #

    # if __name__ == '__main__':

    # t1 = threading.Thread(target=work1)

    # t1.start()

    # time.sleep(1)

    # t2=threading.Thread(target=work2)

    # t2.start()

    '''

    由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,

    所以出现了线程锁,即同一时刻允许一个线程执行操作。线程锁用于锁定资源,可以定义多个锁,像下面的代码,当需要独占

    某一个资源时,任何一个锁都可以锁定这个资源,就好比你用不同的锁都可以把这个相同的门锁住一样。

    由于线程之间是进行随机调度的,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,

    我们因此也称为“线程不安全”。

    为了防止上面情况的发生,就出现了互斥锁(Lock)

    '''

    # def work():

    # global n

    # lock.acquire()

    # temp = n

    # time.sleep(0.1)

    # n = temp-1

    # lock.release()

    #

    #

    # if __name__ == '__main__':

    # lock = Lock()

    # n = 100

    # l = []

    # for i in range(100):

    # p = Thread(target=work)

    # l.append(p)

    # p.start()

    # for p in l:

    # p.join()

    '''

    递归锁:RLcok类的用法和Lock类一模一样,但它支持嵌套,在多个锁没有释放的时候一般会使用RLock类

    '''

    # def func(lock):

    # global gl_num

    # lock.acquire()

    # gl_num += 1

    # time.sleep(1)

    # print(gl_num)

    # lock.release()

    #

    #

    # if __name__ == '__main__':

    # gl_num = 0

    # lock = threading.RLock()

    # for i in range(10):

    # t = threading.Thread(target=func,args=(lock,))

    # t.start()

    '''

    信号量(BoundedSemaphore类)

    互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据,比如厕所有3个坑,

    那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去

    '''

    # def run(n,semaphore):

    # semaphore.acquire() #加锁

    # time.sleep(3)

    # print('run the thread:%sn' % n)

    # semaphore.release() #释放

    #

    #

    # if __name__== '__main__':

    # num=0

    # semaphore = threading.BoundedSemaphore(5) #最多允许5个线程同时运行

    # for i in range(22):

    # t = threading.Thread(target=run,args=('t-%s' % i,semaphore))

    # t.start()

    # while threading.active_count() !=1:

    # pass

    # else:

    # print('----------all threads done-----------')

    '''

    python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下的几个方法:

    clear将flag设置为 False

    set将flag设置为 True

    is_set判断是否设置了flag

    wait会一直监听flag,如果没有检测到flag就一直处于阻塞状态

    事件处理的机制:全局定义了一个Flag,当Flag的值为False,那么event.wait()就会阻塞,当flag值为True,

    那么event.wait()便不再阻塞

    '''

    event = threading.Event()

    def lighter():

    count = 0

    event.set() #初始者为绿灯

    while True:

    if 5 < count <=10:

    event.clear() #红灯,清除标志位

    print("33[41;lmred light is on...033[0m]")

    elif count > 10:

    event.set() #绿灯,设置标志位

    count = 0

    else:

    print('33[42;lmgreen light is on...033[0m')

    time.sleep(1)

    count += 1

    def car(name):

    while True:

    if event.is_set(): #判断是否设置了标志位

    print('[%s] running.....'%name)

    time.sleep(1)

    else:

    print('[%s] sees red light,waiting...'%name)

    event.wait()

    print('[%s] green light is on,start going...'%name)

    # startTime = time.time()

    light = threading.Thread(target=lighter,)

    light.start()

    car = threading.Thread(target=car,args=('MINT',))

    car.start()

    endTime = time.time()

    # print('用时:',endTime-startTime)

    '''

    GIL 全局解释器

    在非python环境中,单核情况下,同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在python中,无论有多少个核

    同时只能执行一个线程。究其原因,这就是由于GIL的存在导致的。

    GIL的全程是全局解释器,来源是python设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们可以

    把GIL看做是“通行证”,并且在一个python进程之中,GIL只有一个。拿不到线程的通行证,并且在一个python进程中,GIL只有一个,

    拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操

    作cpu,而只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的

    python在使用多线程的时候,调用的是c语言的原生过程。

    '''

    '''

    python针对不同类型的代码执行效率也是不同的

    1、CPU密集型代码(各种循环处理、计算等),在这种情况下,由于计算工作多,ticks技术很快就会达到阀值,然后出发GIL的

    释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。

    2、IO密集型代码(文件处理、网络爬虫等设计文件读写操作),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,

    造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序的执行

    效率)。所以python的多线程对IO密集型代码比较友好。

    '''

    '''

    主要要看任务的类型,我们把任务分为I/O密集型和计算密集型,而多线程在切换中又分为I/O切换和时间切换。如果任务属于是I/O密集型,

    若不采用多线程,我们在进行I/O操作时,势必要等待前面一个I/O任务完成后面的I/O任务才能进行,在这个等待的过程中,CPU处于等待

    状态,这时如果采用多线程的话,刚好可以切换到进行另一个I/O任务。这样就刚好可以充分利用CPU避免CPU处于闲置状态,提高效率。但是

    如果多线程任务都是计算型,CPU会一直在进行工作,直到一定的时间后采取多线程时间切换的方式进行切换线程,此时CPU一直处于工作状态,

    此种情况下并不能提高性能,相反在切换多线程任务时,可能还会造成时间和资源的浪费,导致效能下降。这就是造成上面两种多线程结果不能的解释。

    结论:I/O密集型任务,建议采取多线程,还可以采用多进程+协程的方式(例如:爬虫多采用多线程处理爬取的数据);对于计算密集型任务,python此时就不适用了。

    '''

    展开全文
  • python 多线程详解

    2018-06-12 15:57:06
    1、多线程的理解 多进程和多线程都可以执行多个任务,线程是进程的一部分。...2、Python多线程创建 在Python中,同样可以实现多线程,有两个标准模块thread和threading,不过我们主要使用更高级的threadi

    1、多线程的理解

    多进程和多线程都可以执行多个任务,线程是进程的一部分。线程的特点是线程之间可以共享内存和变量,资源消耗少(不过在Unix环境中,多进程和多线程资源调度消耗差距不明显,Unix调度较快),缺点是线程之间的同步和加锁比较麻烦。

    2、Python多线程创建

    在Python中,同样可以实现多线程,有两个标准模块thread和threading,不过我们主要使用更高级的threading模块。使用例子:

    start是启动线程,join是阻塞当前线程,即使得在当前线程结束时,不会退出。从结果可以看到,主线程直到Thread-1结束之后才结束。
    Python中,默认情况下,如果不加join语句,那么主线程不会等到当前线程结束才结束,但却不会立即杀死该线程。如不加join输出如下:

    但如果为线程实例添加t.setDaemon(True)之后,如果不加join语句,那么当主线程结束之后,会杀死子线程。代码:

    如果加上join,并设置等待时间,就会等待线程一段时间再退出:

    3、线程锁和ThreadLocal

    (1)线程锁

    对于多线程来说,最大的特点就是线程之间可以共享数据,那么共享数据就会出现多线程同时更改一个变量,使用同样的资源,而出现死锁、数据错乱等情况。

    假设有两个全局资源,a和b,有两个线程thread1,thread2. thread1占用a,想访问b,但此时thread2占用b,想访问a,两个线程都不释放此时拥有的资源,那么就会造成死锁。

    对于该问题,出现了Lock。 当访问某个资源之前,用Lock.acquire()锁住资源,访问之后,用Lock.release()释放资源。

    用finally的目的是防止当前线程无线占用资源。

    (2)ThreadLocal

    介绍完线程锁,接下来出场的是ThreadLocal。当不想将变量共享给其他线程时,可以使用局部变量,但在函数中定义局部变量会使得在函数之间传递特别麻烦。ThreadLocal是非常牛逼的东西,它解决了全局变量需要枷锁,局部变量传递麻烦的两个问题。通过在线程中定义:
    local_school = threading.local()
    此时这个local_school就变成了一个全局变量,但这个全局变量只在该线程中为全局变量,对于其他线程来说是局部变量,别的线程不可更改。 def process_thread(name):# 绑定ThreadLocal的student: local_school.student = name

    这个student属性只有本线程可以修改,别的线程不可以。代码:

    从代码中也可以看到,可以将ThreadLocal理解成一个dict,可以绑定不同变量。
    ThreadLocal用的最多的地方就是每一个线程处理一个HTTP请求,在Flask框架中利用的就是该原理,它使用的是基于Werkzeug的LocalStack。

    4、Map实现多线程:

    对于多线程的使用,我们经常是用thread来创建,比较繁琐:

    如果要创建更多的线程,那就要一一加到里面,操作麻烦,代码可读性也变差。在Python中,可以使用map函数简化代码。map可以实现多任务的并发,简单示例:

    map将urls的每个元素当做参数分别传给urllib2.urlopen函数,并最后把结果放到results列表中,map 函数一手包办了序列操作、参数传递和结果保存等一系列的操作。 其原理:

    map函数负责将线程分给不同的CPU。

    在 Python 中有个两个库包含了 map 函数: multiprocessing 和它鲜为人知的子库 multiprocessing.dummy.dummy 是 multiprocessing 模块的完整克隆,唯一的不同在于 multiprocessing 作用于进程,而 dummy 模块作用于线程。代码:

    • pool = ThreadPool()创建了线程池,其默认值为当前机器 CPU 的核数,可以指定线程池大小,不是越多越好,因为越多的话,线程之间的切换也是很消耗资源的。
    • results = pool.map(urllib2.urlopen,urls) 该语句将不同的url传给各自的线程,并把执行后结果返回到results中。

    代码清晰明了,巧妙得完成Threading模块完成的功能。

    5、Python多线程的缺陷:

    上面说了那么多关于多线程的用法,但Python多线程并不能真正能发挥作用,因为在Python中,有一个GIL,即全局解释锁,该锁的存在保证在同一个时间只能有一个线程执行任务,也就是多线程并不是真正的并发,只是交替得执行。假如有10个线程炮在10核CPU上,当前工作的也只能是一个CPU上的线程。

    6、Python多线程的应用场景。

    虽然Python多线程有缺陷,总被人说成是鸡肋,但也不是一无用处,它很适合用在IO密集型任务中。I/O密集型执行期间大部分是时间都用在I/O上,如数据库I/O,较少时间用在CPU计算上。因此该应用场景可以使用Python多线程,当一个任务阻塞在IO操作上时,我们可以立即切换执行其他线程上执行其他IO操作请求。

    总结:Python多线程在IO密集型任务中还是很有用处的,而对于计算密集型任务,应该使用Python多进程。

    转自:http://python.jobbole.com/85050/

    展开全文
  • python多线程详解

    2021-01-29 18:22:57
    二、线程基础1、线程的状态线程有5种状态,状态转换的过程如下图所示:2、线程同步(锁)多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。考虑这样...

    一、进程与线程关系

    一个进程至少包含一个线程。

    二、线程基础

    1、线程的状态

    线程有5种状态,状态转换的过程如下图所示:

    7a461d48d65903961f38caf591785849.png

    2、线程同步(锁)

    多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。考虑这样一种情况:一个列表里所有元素都是0,线程”set”从后向前把所有元素改成1,而线程”print”负责从前往后读取列表并打印。那么,可能线程”set”开始改的时候,线程”print”便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。

    锁有两种状态——锁定和未锁定。每当一个线程比如”set”要访问共享数据时,必须先获得锁定;如果已经有别的线程比如”print”获得锁定了,那么就让线程”set”暂停,也就是同步阻塞;等到线程”print”访问完毕,释放锁以后,再让线程”set”继续。经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。

    线程与锁的交互如下图所示:

    3ef442a790361a6c9fc539dfd07c0506.png

    3、线程通信(条件变量)

    然而还有另外一种尴尬的情况:列表并不是一开始就有的;而是通过线程”create”创建的。如果”set”或者”print” 在”create”还没有运行的时候就访问列表,将会出现一个异常。使用锁可以解决这个问题,但是”set”和”print”将需要一个无限循环——他们不知道”create”什么时候会运行,让”create”在运行后通知”set”和”print”显然是一个更好的解决方案。于是,引入了条件变量。

    条件变量允许线程比如”set”和”print”在条件不满足的时候(列表为None时)等待,等到条件满足的时候(列表已经创建)发出一个通知,告诉”set” 和”print”条件已经有了,你们该起床干活了;然后”set”和”print”才继续运行。

    线程与条件变量的交互如下图所示:

    8b35120e2430ff2d081dfd09c521b404.png

    4、线程运行和阻塞的状态转换

    最后看看线程运行和阻塞状态的转换

    2fa80e5a067356d42517edd5b90b942e.png

    阻塞有三种情况:

    同步阻塞(锁定池)是指处于竞争锁定的状态,线程请求锁定时将进入这个状态,一旦成功获得锁定又恢复到运行状态;

    等待阻塞(等待池)是指等待其他线程通知的状态,线程获得条件锁定后,调用“等待”将进入这个状态,一旦其他线程发出通知,线程将进入同步阻塞状态,再次竞争条件锁定;

    而其他阻塞是指调用time.sleep()、anotherthread.join()或等待IO时的阻塞,这个状态下线程不会释放已获得的锁定。

    四、threading

    threading基于Java的线程模型设计。锁(Lock)和条件变量(Condition)在Java中是对象的基本行为(每一个对象都自带了锁和条件变量),而在Python中则是独立的对象。Python Thread提供了Java Thread的行为的子集;没有优先级、线程组,线程也不能被停止、暂停、恢复、中断。Java Thread中的部分被Python实现了的静态方法在threading中以模块方法的形式提供。

    threading 模块提供的常用方法:

    threading.currentThread(): 返回当前的线程变量。

    threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

    threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

    threading模块提供的类:

    Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local.

    1、Thread

    Thread是线程类,与Java类似,有两种使用方法,直接传入要运行的方法或从Thread继承并覆盖run():

    # encoding: UTF-8

    import threading

    # 方法1:将要执行的方法作为参数传给Thread的构造方法

    def func():

    print 'func() passed to Thread'

    t = threading.Thread(target=func)

    t.start()

    # 方法2:从Thread继承,并重写run()

    class MyThread(threading.Thread):

    def run(self):

    print 'MyThread extended from Thread'

    t = MyThread()

    t.start()

    构造方法:

    Thread(group=None, target=None, name=None, args=(), kwargs={})

    group: 线程组,目前还没有实现,库引用中提示必须是None;

    target: 要执行的方法;

    name: 线程名;

    args/kwargs: 要传入方法的参数。

    实例方法:

    isAlive(): 返回线程是否在运行。正在运行指启动后、终止前。

    get/setName(name): 获取/设置线程名。

    is/setDaemon(bool): 获取/设置是否守护线程。初始值从创建该线程的线程继承。当没有非守护线程仍在运行时,程序将终止。

    start(): 启动线程。

    join([timeout]): 阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout(可选参数)。

    一个使用join()的例子:

    # encoding: UTF-8

    import threading

    import time

    def context(tJoin):

    print 'in threadContext.'

    tJoin.start()

    # 将阻塞tContext直到threadJoin终止。

    tJoin.join()

    # tJoin终止后继续执行。

    print 'out threadContext.'

    def join():

    print 'in threadJoin.'

    time.sleep(1)

    print 'out threadJoin.'

    # tJoin和tContext分别为两个不同的线程

    tJoin = threading.Thread(target=join)

    tContext = threading.Thread(target=context, args=(tJoin,))

    tContext.start()

    运行结果:

    in threadContext.

    in threadJoin.

    out threadJoin.

    out threadContext

    2、Lock

    Lock(指令锁)是可用的最低级的同步指令。Lock处于锁定状态时,不被特定的线程拥有。Lock包含两种状态——锁定和非锁定,以及两个基本的方法。

    可以认为Lock有一个锁定池,当线程请求锁定时,将线程至于池中,直到获得锁定后出池。池中的线程处于状态图中的同步阻塞状态。

    构造方法:

    Lock()

    实例方法:

    acquire([timeout]): 使线程进入同步阻塞状态,尝试获得锁定。

    release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。

    # encoding: UTF-8

    import threading

    import time

    data = 0

    lock = threading.Lock()

    def func():

    global data

    print '%s acquire lock...' % threading.currentThread().getName()

    # 调用acquire([timeout])时,线程将一直阻塞,

    # 直到获得锁定或者直到timeout秒后(timeout参数可选)。

    # 返回是否获得锁。

    if lock.acquire():

    print '%s get the lock.' % threading.currentThread().getName()

    data += 1

    time.sleep(2)

    print '%s release lock...' % threading.currentThread().getName()

    # 调用release()将释放锁。

    lock.release()

    t1 = threading.Thread(target=func)

    t2 = threading.Thread(target=func)

    t3 = threading.Thread(target=func)

    t1.start()

    t2.start()

    t3.start()

    多运行几次,你会看到打印的信息顺序并不一致,这就证实了线程在锁定池中谁将获得锁运行是由系统调度决定(随机,不确定)

    RLock

    RLock(可重入锁)是一个可以被同一个线程请求多次的同步指令。RLock使用了“拥有的线程”和“递归等级”的概念,处于锁定状态时,RLock被某个线程拥有。拥有RLock的线程可以再次调用acquire(),释放锁时需要调用release()相同次数。

    可以认为RLock包含一个锁定池和一个初始值为0的计数器,每次成功调用 acquire()/release(),计数器将+1/-1,为0时锁处于未锁定状态。

    构造方法:

    RLock()

    实例方法:

    acquire([timeout])/release(): 跟Lock差不多。

    # encoding: UTF-8

    import threading

    import time

    rlock = threading.RLock()

    def func():

    # 第一次请求锁定

    print '%s acquire lock...' % threading.currentThread().getName()

    if rlock.acquire():

    print '%s get the lock.' % threading.currentThread().getName()

    time.sleep(2)

    # 第二次请求锁定

    print '%s acquire lock again...' % threading.currentThread().getName()

    if rlock.acquire():

    print '%s get the lock.' % threading.currentThread().getName()

    time.sleep(2)

    # 第一次释放锁

    print '%s release lock...' % threading.currentThread().getName()

    rlock.release()

    time.sleep(2)

    # 第二次释放锁

    print '%s release lock...' % threading.currentThread().getName()

    rlock.release()

    t1 = threading.Thread(target=func)

    t2 = threading.Thread(target=func)

    t3 = threading.Thread(target=func)

    t1.start()

    t2.start()

    t3.start()

    4、Condition

    Condition(条件变量)通常与一个锁关联。需要在多个Contidion中共享一个锁时,可以传递一个Lock/RLock实例给构造方法,否则它将自己生成一个RLock实例。

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

    构造方法:

    Condition([lock/rlock])

    实例方法:

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

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

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

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

    例子是很常见的生产者/消费者模式:

    # encoding: UTF-8

    import threading

    import time

    # 商品

    product = None

    # 条件变量

    con = threading.Condition()

    # 生产者方法

    def produce():

    global product

    if con.acquire():

    while True:

    if product is None:

    print 'produce...'

    product = 'anything'

    # 通知消费者,商品已经生产

    con.notify()

    # 等待通知

    con.wait()

    time.sleep(2)

    # 消费者方法

    def consume():

    global product

    if con.acquire():

    while True:

    if product is not None:

    print 'consume...'

    product = None

    # 通知生产者,商品已经没了

    con.notify()

    # 等待通知

    con.wait()

    time.sleep(2)

    t1 = threading.Thread(target=produce)

    t2 = threading.Thread(target=consume)

    t2.start()

    t1.start()

    5、Semaphore/BoundedSemaphore

    Semaphore(信号量)是计算机科学史上最古老的同步指令之一。Semaphore管理一个内置的计数器,每当调用acquire()时-1,调用release() 时+1。计数器不能小于0;当计数器为0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。

    基于这个特点,Semaphore经常用来同步一些有“访客上限”的对象,比如连接池。

    BoundedSemaphore 与Semaphore的唯一区别在于前者将在调用release()时检查计数器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。

    构造方法:

    Semaphore(value=1): value是计数器的初始值。

    实例方法:

    acquire([timeout]): 请求Semaphore。如果计数器为0,将阻塞线程至同步阻塞状态;否则将计数器-1并立即返回。

    release(): 释放Semaphore,将计数器+1,如果使用BoundedSemaphore,还将进行释放次数检查。release()方法不检查线程是否已获得 Semaphore。

    # encoding: UTF-8

    import threading

    import time

    # 计数器初值为2

    semaphore = threading.Semaphore(2)

    def func():

    # 请求Semaphore,成功后计数器-1;计数器为0时阻塞

    print '%s acquire semaphore...' % threading.currentThread().getName()

    if semaphore.acquire():

    print '%s get semaphore' % threading.currentThread().getName()

    time.sleep(4)

    # 释放Semaphore,计数器+1

    print '%s release semaphore' % threading.currentThread().getName()

    semaphore.release()

    t1 = threading.Thread(target=func)

    t2 = threading.Thread(target=func)

    t3 = threading.Thread(target=func)

    t4 = threading.Thread(target=func)

    t1.start()

    t2.start()

    t3.start()

    t4.start()

    time.sleep(2)

    # 没有获得semaphore的主线程也可以调用release

    # 若使用BoundedSemaphore,t4释放semaphore时将抛出异常

    print 'MainThread release semaphore without acquire'

    semaphore.release()

    6、Event

    Event(事件)是最简单的线程通信机制之一:一个线程通知事件,其他线程等待事件。Event内置了一个初始为False的标志,当调用set()时设为True,调用clear()时重置为 False。wait()将阻塞线程至等待阻塞状态。

    Event其实就是一个简化版的 Condition。Event没有锁,无法使线程进入同步阻塞状态。

    构造方法:

    Event()

    实例方法:

    isSet(): 当内置标志为True时返回True。

    set(): 将标志设为True,并通知所有处于等待阻塞状态的线程恢复运行状态。

    clear(): 将标志设为False。

    wait([timeout]): 如果标志为True将立即返回,否则阻塞线程至等待阻塞状态,等待其他线程调用set()。

    # encoding: UTF-8

    import threading

    import time

    event = threading.Event()

    def func():

    # 等待事件,进入等待阻塞状态

    print '%s wait for event...' % threading.currentThread().getName()

    event.wait()

    # 收到事件后进入运行状态

    print '%s recv event.' % threading.currentThread().getName()

    t1 = threading.Thread(target=func)

    t2 = threading.Thread(target=func)

    t1.start()

    t2.start()

    time.sleep(2)

    # 发送事件通知

    print 'MainThread set event.'

    event.set()

    Timer

    Timer(定时器)是Thread的派生类,用于在指定时间后调用一个方法。

    构造方法:

    Timer(interval, function, args=[], kwargs={})

    interval: 指定的时间

    function: 要执行的方法

    args/kwargs: 方法的参数

    实例方法:

    Timer从Thread派生,没有增加实例方法

    # encoding: UTF-8

    import threading

    def func():

    print 'hello timer!'

    timer = threading.Timer(5, func)

    timer.start()

    8、local

    local是一个小写字母开头的类,用于管理 thread-local(线程局部的)数据。对于同一个local,线程无法访问其他线程设置的属性;线程设置的属性不会被其他线程设置的同名属性替换。

    可以把local看成是一个“线程-属性字典”的字典,local封装了从自身使用线程作为 key检索对应的属性字典、再使用属性名作为key检索属性值的细节。

    # encoding: UTF-8

    import threading

    local = threading.local()

    local.tname = 'main'

    def func():

    local.tname = 'notmain'

    print local.tname

    t1 = threading.Thread(target=func)

    t1.start()

    t1.join()

    print local.tname

    熟练掌握Thread、Lock、Condition就可以应对绝大多数需要使用线程的场合,某些情况下local也是非常有用的东西。本文的最后使用这几个类展示线程基础中提到的场景:

    # encoding: UTF-8

    import threading

    alist = None

    condition = threading.Condition()

    def doSet():

    if condition.acquire():

    while alist is None:

    condition.wait()

    for i in range(len(alist))[::-1]:

    alist[i] = 1

    condition.release()

    def doPrint():

    if condition.acquire():

    while alist is None:

    condition.wait()

    for i in alist:

    print i,

    print

    condition.release()

    def doCreate():

    global alist

    if condition.acquire():

    if alist is None:

    alist = [0 for i in range(10)]

    condition.notifyAll()

    condition.release()

    tset = threading.Thread(target=doSet,name='tset')

    tprint = threading.Thread(target=doPrint,name='tprint')

    tcreate = threading.Thread(target=doCreate,name='tcreate')

    tset.start()

    tprint.start()

    tcreate.start()

    展开全文
  • 主要介绍了Python多线程下的list的相关资料,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
  • python多线程详解 一、线程介绍 什么是线程 线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必...
  • python多线程教程:python多线程详解

    千次阅读 2020-02-03 11:49:20
    文章目录一、线程介绍二...python多线程详解 一、线程介绍 什么是线程 线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源...
  • 多线程类似于同时执行多个不同程序,多线程运行有如下优点:(推荐学习:web前端视频教程)使用线程可以把占据长时间的程序中的任务放到后台去处理。用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的...
  • 今天小编就为大家分享一篇对Python多线程读写文件加锁的实例详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 20,743
精华内容 8,297
关键字:

python多线程详解

python 订阅
友情链接: MS-Visual-CPP-6.0.rar