zoukankan      html  css  js  c++  java
  • Python之线程

    一、线程的起源

      1,进程

      之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有将程序装载到内存中,系统为其分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次活动,属于动态概念。在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发的执行。这样的设计大大提高了CPU的利用率。

      2,为什么还出现线程

      例子:pycharm三个任务:键盘输入、屏幕输出、自动保存硬盘,如果三个任务是同步工作的,那在键盘输入的时候我们就看不到屏幕输入,而我们需要在键盘输入的时候同时在屏幕上显示,还能在硬盘保存。方案一,此时我们可以开三个进程来完成,三个进程之间还要通过进程通信的介质帮助完成。方案二,其实我们还可以用线程来完成,此时我们可以在一个进程中开三个线程,由于线程间是资源共享的,所以不用借助介质就能完成数据交换。我们对比两种方案,方案一要开三个进程,如果有10000个任务,那就要开10000个进程,开进程很占内存的,而且开进程很耗时间的,还要借助介质才能实现通信,而方案就不一样了,只需开一个进程,节省了内存空间,缩短了时间,自身就可以实现数据共享,那肯定选择方案二,从而就凸显了线程的必要性。

      3,线程的出现

      随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤销与切换存在较大的时空开销,因此引入轻型进程;二是由于对称多处理机出现,可以满足多个运行单位,二多个进程并行开销过大,出现了能独立运行的基本单位——线程,进程是资源分配的最小单位,线程是CPU调度的最小单位,每个进程中至少有一个线程,进程只是把资源集中到一起,而线程才是CPU上的执行单位。

      4,线程与进程的关系

      线程就是进程的组成单元,每一个进程至少有一个线程,同一个进程里的多个线程,可以共享进程里的资源,而且线程间切换比进程间切换快很多,进程不是一个可执行的实体,真正去执行程序的是线程,可以理解进程解释装线程的容器。

    二,线程的创建方法

      由于线程诞生于进程,所以说线程的创建和进程一模一样,只是引用的模块不一样而已。

      1,方法一

    from threading import Thread             #和进程相比,就是线程引入的是Thread模块
    def fun1(i):
        print('你是%s'%(i))
    if __name__ == '__main__':
        t=Thread(target=fun1,args=(1,))
        t.start()
        print('dddddddd')

      2,方法二

    from threading import Thread
    class Mythread(Thread):
        def __init__(self,nn):
            super().__init__()
            self.nn=nn
        def run(self):
            print('nishi%s'%self.nn)
    if __name__ == '__main__':
        t=Mythread('haha')
        t.start()
        print('dfssd')

    三、多进程与多线程的效率对比

    from multiprocessing import Process
    from threading import Thread
    import time
    def fun():
        print('ffff')
    if __name__ == '__main__':
        l1=[]
        t_s_t=time.time()
        for i in range(100):
            t=Thread(target=fun,)
            l1.append(t)
            t.start()
        [tt.join() for tt in l1]
        t_e_t=time.time()
        l2 = []
        p_s_t = time.time()
        for i in range(100):
            p = Process(target=fun, )
            l1.append(p)
            p.start()
        [pp.join() for pp in l2]
        p_e_t = time.time()
        print('线程',t_e_t-t_s_t)
        print('进程',p_e_t-p_s_t)

    线程 0.04086899757385254
    进程 3.268401861190796

      从上面的结果看,线程的效率比进程高很多,这主要是创建、销毁进程和进程间切换太耗时间。

    四、线程的其他方法

    from threading import Thread,current_thread
    import threading,time
    def fun(i):
        time.sleep(2)
        print('我是%s号'%i)
        print('%s'%current_thread().getName())     #获取线程的name
        print('%s'%current_thread().ident)          #获取线程的id
    if __name__ == '__main__':
        for i in range(10):
            t=Thread(target=fun,args=(i,))
            t.start()
        print(threading.enumerate())    #返回一个正在运行线程的列表
        print(threading.active_count())  #返回正在运行线程的数量

    五、死锁现象

    from threading import Thread,Lock,RLock
    import time
    def fun(loa,lob):
        loa.acquire()
        time.sleep(1)
        print('aaaaa')
        lob.acquire()
        print('bbbbb')
        lob.release()
        loa.release()
    def fun1(loa,lob):
        lob.acquire()
        time.sleep(1)
        print('cccccc')
        loa.acquire()
        print('ddddd')
        loa.release()
        lob.release()
    if __name__ == '__main__':
        # loa=Lock()                #当我们用Lock时就会出现死锁现象,由于是异步执行的,fun1拿到loa,fun2拿到lob,然后fun1再去拿lob,fun2再去拿loa,但现在两把锁都被对方拿着,还没释放,从而形成死锁
        # lob=Lock()
        loa=lob=RLock()             #当我们用Rlock时,若fun1先抢到,就必须等fun1用完,fun2才能拿到,这称为递归锁
        t1=Thread(target=fun,args=(loa,lob))
        t2=Thread(target=fun1,args=(loa,lob))
        t1.start()
        t2.start()

    六、主进程和主线程的结束标志

      主进程在主进程的代码执行完就结束,而主线程要等到在同一进程中的非守护线程代码执行完毕才结束。

    主进程的结束标志
    from
    multiprocessing import Process import time def fun1(): time.sleep(2) print('我是fun1') def fun2(): time.sleep(3) print('我是fun2') if __name__ == '__main__': p1=Process(target=fun1,) p2=Process(target=fun2,) p1.daemon=True #p1现在是守护进程,在主进程结束后,随之结束 p1.start() p2.start() print('我是主进程') #主进程会在这句代码执行完后结束,p1也会跟着结束,虽说p1还没执行完,但是p1直接被干死了
    结果如下

    我是主进程
    我是fun2

     
    主线程结束标志
    from
    threading import Thread import time def fun1(): time.sleep(2) print('我是fun1') def fun2(): time.sleep(3) print('我是fun2') if __name__ == '__main__': t1=Thread(target=fun1,) t2=Thread(target=fun2,) t1.daemon=True #把t1设为守护线程 t1.start() t2.start() print('我是主线程') #这句代码执行完后,主线程还没结束,主线程要等待非守护线程t2执行完毕后才结束,因为t2执行时间比t1长,所以这次t1也会执行完毕

     七、线程的信号量、事件

      线程的信号量、事件和进程的信号量、事件的用法一样,从threading中引入Semaphore、Event。

    八、线程的队列

    import queue
    q=queue.Queue(3)    #先进先出
    q.put(2)
    q.put(4)
    print(q.get())
    q1=queue.LifoQueue(4)    #先进后出队列
    q1.put(5)
    q1.put(6)
    print(q1.get())
    q2=queue.PriorityQueue(2)   #优先级队列
    q2.put((1,'a'))
    q2.put((-1,8))
    q2.put((1,'g'))
    print(q2.get())
    print(q2.get())
    print(q2.get())

    九、GIL锁

      GIL锁是加在一个进程里面的,而且每个进程里面都有,GIL锁锁定内容是整个一条线程,实现的功能就是在同一进程同一时间只能允许一条线程使用CPU,当线程运行到IO时,切换到下一个线程,这其实是Cpython工作效率比较慢的一大原因,也是一大弊端,但我们基本上的应用场景都是IO密集型的,线程之间来回切换,也相当于实现线程的并发,所以还是Cpython还是够用的

    十、线程池

      现在我们重新学习一个模块concurrent.futures,在这个模块中提供了线程池和进程池,两个的用法也是一样的

    concurrent.futures模块提供了高度封装的异步调用接口
    ThreadPoolExecutor:线程池,提供异步调用
    ProcessPoolExecutor: 进程池,提供异步调用
    Both implement the same interface, which is defined by the abstract Executor class.
    
    #2 基本方法
    #submit(fn, *args, **kwargs)
    异步提交任务
    
    #map(func, *iterables, timeout=None, chunksize=1) 
    取代for循环submit的操作
    
    #shutdown(wait=True) 
    相当于进程池的pool.close()+pool.join()操作
    wait=True,等待池内所有任务执行完毕回收完资源后才继续
    wait=False,立即返回,并不会等待池内的任务执行完毕
    但不管wait参数为何值,整个程序都会等到所有任务执行完毕
    submit和map必须在shutdown之前
    
    #result(timeout=None)
    取得结果
    
    #add_done_callback(fn)
    回调函数

      1,线程池和进程池

    import time
    import os
    import threading
    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    
    def func(n):
        time.sleep(2)
        print('%s打印的:'%(threading.get_ident()),n)
        return n*n
    tpool = ThreadPoolExecutor(max_workers=5) #默认一般起线程的数据不超过CPU个数*5
    # tpool = ProcessPoolExecutor(max_workers=5) #进程池的使用只需要将上面的ThreadPoolExecutor改为ProcessPoolExecutor就行了,其他都不用改
    #异步执行
    t_lst = []
    for i in range(5):
        t = tpool.submit(func,i) #提交执行函数,返回一个结果对象,i作为任务函数的参数 def submit(self, fn, *args, **kwargs):  可以传任意形式的参数
        t_lst.append(t)  #
        # print(t.result())
        #这个返回的结果对象t,不能直接去拿结果,不然又变成串行了,可以理解为拿到一个号码,等所有线程的结果都出来之后,我们再去通过结果对象t获取结果
    tpool.shutdown() #起到原来的close阻止新任务进来 + join的作用,等待所有的线程执行完毕
    print('主线程')
    for ti in t_lst:
        print('>>>>',ti.result())
    
    # 我们还可以不用shutdown(),用下面这种方式
    # while 1:
    #     for n,ti in enumerate(t_lst):
    #         print('>>>>', ti.result(),n)
    #     time.sleep(2) #每个两秒去去一次结果,哪个有结果了,就可以取出哪一个,想表达的意思就是说不用等到所有的结果都出来再去取,可以轮询着去取结果,因为你的任务需要执行的时间很长,那么你需要等很久才能拿到结果,
    通过这样的方式可以将快速出来的结果先拿出来。如果有的结果对象里面还没有执行结果,那么你什么也取不到,这一点要注意,不是空的,是什么也取不到,那怎么判断我已经取出了哪一个的结果,可以通过枚举enumerate来搞,
    记录你是哪一个位置的结果对象的结果已经被取过了,取过的就不再取了
    #结果分析: 打印的结果是没有顺序的,因为到了func函数中的sleep的时候线程会切换,谁先打印就没准儿了,但是最后的我们通过结果对象取结果的时候拿到的是有序的,因为我们主线程进行for循环的时候,我们是按顺序将结果对象添加到列表中的。 # 37220打印的: 0 # 32292打印的: 4 # 33444打印的: 1 # 30068打印的: 2 # 29884打印的: 3 # 主线程 # >>>> 0 # >>>> 1 # >>>> 4 # >>>> 9 # >>>> 16

      2,map的使用

    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    import threading
    import os,time,random
    def task(n):
        print('%s is runing' %threading.get_ident())
        time.sleep(random.randint(1,3))
        return n**2
    
    if __name__ == '__main__':
    
        executor=ThreadPoolExecutor(max_workers=3)
    
        # for i in range(11):
        #     future=executor.submit(task,i)
    
        s = executor.map(task,range(1,5)) #map取代了for+submit
        print([i for i in s])

      3,回调函数的使用

    import time
    import os
    import threading
    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    
    def func(n):
        time.sleep(2)
        return n*n
    
    def call_back(m):
        print('结果为:%s'%(m.result()))
    
    tpool = ThreadPoolExecutor(max_workers=5)
    t_lst = []
    for i in range(5):
        t = tpool.submit(func,i).add_done_callback(call_back)
  • 相关阅读:
    通过Navicat导入SQLServer的MDF文件和LDF文件
    ubantu系统出现登录界面死循环处理办法
    ubantu系统修改权限失败,导致只能客人会话登录解决办法
    redis基础
    ubantu安装MySQL,并未出现设置root密码的提示--》少年,请不要乱改密码!
    ngx_http_access_module模块说明
    一些常用的ngx_http_core_module介绍
    八、location匹配规则
    七、nginx的虚拟主机配置
    六、nginx的配置文件说明
  • 原文地址:https://www.cnblogs.com/12345huangchun/p/10054232.html
Copyright © 2011-2022 走看看