美文网首页Python实例教程
Python的 并发、并行

Python的 并发、并行

作者: 欢喜明 | 来源:发表于2018-05-06 18:20 被阅读0次

    记录:

    并发:在一个时间段,处理多个任务,单核也可以并发(CPU分时间片);

    并行:在同一个时刻,处理多个任务,必须多核才能并行;

    一、Python实现并发的手段:

    1、操作系统提供:进程、线程;

    2、编程语言提供:协程:用户空间的调度(py3);

    题外话:

    现在的操作系统,进程和线程的区别越来越小,因为进程越来越轻了;实际上,Linux的线程是通过进程实现的;

    二、Python的进程和线程的区别:

    Python每个进程都会启动一个解释器;

    Python每个线程(一个进程下面的)共享一个解释器;

    ps:Python没有提供主动停止线程的方法的;只能等线程处理完毕,或者主线程结束;所以在线程逻辑里面一定要写退出逻辑;

    三、logging库:

    通常用logging代替print,因为logging是线程安全的,在多线程下,输出表现正常,而print是非线程安全的,在多线程下,输出表现会出现异常,例如,T1没输出完毕,T2又进行输出了;

    四、Python 线程的 daemon 属性:

    默认是false;

    daemon:守护、守护进程;

    守护进程:Daemon()程序是一直运行的服务端程序,又称为守护进程。通常在系统后台运行,没有控制终端,不与前台交互,Daemon程序一般作为系统服务使用。Daemon是长时间运行的进程,通常在系统启动后就运行,在系统关闭时才结束。

    知识点一:

    当一个进程启动之后,会默认产生一个主线程,因为线程是程序执行流的最小单元,当设置多线程时,主线程会创建多个子线程,在python中,默认情况下(其实就是setDaemon(False)),主线程执行完自己的任务以后,就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束;

    知识点二:

    当我们使用setDaemon(True)方法,设置子线程为守护线程时,主线程一旦执行结束,则全部线程全部被终止执行,可能出现的情况就是,子线程的任务还没有完全执行结束,就被迫停止;

    知识点三:

    join所完成的工作就是线程同步,即主线程任务结束之后,进入阻塞(等待)状态,一直等待其他的子线程执行结束之后,主线程在终止;

    知识点四:

    join有一个timeout参数:

    当设置守护线程时,含义是主线程对于子线程等待timeout的时间将会杀死该子线程,最后退出程序。所以说,如果有10个子线程,全部的等待时间就是每个timeout的累加和。简单的来说,就是给每个子线程一个timeout的时间,让他去执行,时间一到,不管任务有没有完成,直接杀死。

    没有设置守护线程时,主线程将会等待timeout的累加和这样的一段时间,时间一到,主线程结束,但是并没有杀死子线程,子线程依然可以继续执行,直到子线程全部结束,程序退出。

    t1.join(),子线程设置这个就是让主线程阻塞(等待),那么在哪里设置了 t1.join(),主线程就在这里等待t1执行完毕,再继续往下执行;因为两个线程顺序完成,看起来象一个线程,所以称为线程的合并,其实也是让线程同步了;

    注意(看到的结果是这样,解释可能不太正确):

    (1)看图:

    t1设置执行2秒;t2设置执行3秒;目的是为了t1先于t2执行完毕;

    t1没有设置daemon线程,所以当主线程结束后,t1仍然会继续执行完毕,所以查看结果:当输出 main end ... 后,仍然有 t1 out ... 输出;

    t2设置为daemon线程,所以当主线程结束后,t2也被主线程杀死了(t1已结执行完毕),跟着结束了,程序(进程)也结束了,所以没有 t2 out... 输出;

    注意,这里一定要保证t1(非daemon子线程)先于t2执行完毕;因为如果t1要比t2耗时长的话,因为 t1是非daemon子线程,即是主线程结束了,t1仍然要继续执行的,所以整个程序(进程)并不会结束,也就没有杀死t2,导致t2也会执行完毕;看下面:

    可以看到:即是t2是daemon子线程,但是仍然执行完毕了,并没有因为主线程的退出而被杀死,因为t1的执行时间比t2长;

    ps:如果不是以继承的方式创建线程,那么run方法和start方法只能执行其中一个;通常也不建议使用继承的方式;start方法是子线程执行的,启动子线程;

    要注意:主线程、父线程、子线程的关系;

    五、Python中的ThreadLocal变量

    注意,并不是子线程要执行的目标函数里面的局部变量,虽然每个线程即使是执行同一个函数,都会有自己的内存的,互不干扰,但是函数里面的局部变量,就是属于函数的,到了另外一个函数不会认识此局部变量;

    而Python的threadlocal变量,是属于线程的,此线程调用的所有函数都认识threadlocal变量,eg.:

    global_data = threading.local()

    def show():

            print (threading.current_thread().getName(), global_data.num

    def thread_cal():

            global_data.num = 0

            for _ in xrange(1000):

                global_data.num += 1

            show()

    每个线程都可以通过 global_data.num 获得自己独有的数据,并且每个线程读取到的 global_data 都不同,真正做到线程之间的隔离。其他线程是不认识global_data.num的;

    六、线程同步方式(前三个方式需要在具体了解)

    线程的同步,有一层意思就是:线程之间的通讯,线程总是有某种关联,才需要同步的,而同步就意味着阻塞(等待);

    1、event

    两个线程之间事件的通知,我发现了一个事件,set,你就可以查看。

    2、lock (rlock?)

    保护共享资源;

    3、condition

    生产者消费者模式;生产者唤醒消费者;

    4、Barrier(栅栏)

    就是线程们在等待,直到线程数满足设定的数量之和,才继续执行;

    可以使用此方法开发并发测试工具;

    一些关键语句:

    barrier = threading.Barrier(3)  #等齐3个线程

    worker_id = barrier.wait()  #线程执行到这一句,就等着,满足数量之后,再往下走

    barrier.n_waiting  #现在在等待的线程数量

    barrier.abort()  #通知已经在等待的线程,不必再等了,我执行不到wait()了(任何一个线程执行都可以),当abort()方法被执行,wait()方法就会抛出异常

    5、semaphore (信号量)

    s = threading.Semaphore(2)

    s.acquire() # 获得,在线程中,也是锁住某个变量,自己此刻独占;

    输出:True  # 成功锁住

    s.acquire(False) # 再来一次

    输出:True  # 成功锁住

    s.acquire(False) # 再来一次

    输出:False # 没有锁

    锁是信号量的特例:为1的信号量;

    信号量也是对共享资源的保护,但是和锁不一样,锁限制只有一个线程可以访问共享资源,而信号量限制指定个线程可以访问共享资源;所以信号量可以用于做连接池的功能;

    七、异步编程

    1、概念

    同步、异步:

    发生在函数调用的时候,是否得到直接最终结果;

    得到直接最终结果的是:同步调用;

    不得到直接最终结果的是:异步调用;

    阻塞、非阻塞:

    发生在函数调用的时候,是否立刻返回;

    立刻返回:非阻塞调用;

    不立刻返回:阻塞调用;

    ps:同步、异步 与 阻塞、非阻塞 在概念上是不相关的;

    同步、异步:关注的是结果;

    阻塞、非阻塞:关注的是是否等待;

    ps:异步非阻塞是最好的性能咯;

    同步IO、异步IO、IO多路复用:

    相关文章

      网友评论

        本文标题:Python的 并发、并行

        本文链接:https://www.haomeiwen.com/subject/uzrurftx.html