1.并发与并行
* 多个任务轮换在CPU上跑叫并发
* 多个任务在多个CPU上跑,没有交替执行的
* 状态叫并行。通常情况下都是并发,即使是多核。
* 而控制进程先执行谁后执行谁通过操作系统的调度算法。
目前已知的调度算法:时间片轮转、优先级调度等
描述的是进程与CPU的关系
2.多任务、多进程
原来的叫父进程(主进程)、后创建出来的叫子进程
3.可以用os.fork()可以创建子进程,赋值给pid然后通过if条件判断,可以让两个进程执行不同的程序。
* 每调用一次fork()进程×2。
get_pid 获取当前进程值,杀进程是kill+pid
4.父子进程与子进程谁先执行谁后执行是不确定的。
5.创建子进程会跑同样一份代码,但是进程里的变量互不影响,自己执行自己的。
6.由于windows里面没有fork(),创意创建子进程还有另外一种方式,那就是从multiprocessing模块中导入 Process类
然后定义一个函数,之后使用Process(target=函数名)来创建一个子进程,在将A = Process(target=函数名)附给一个变量
A.Start()就可以执行该子进程
7.用Process创建子进程与fork()区别在于Process创建出来主进程一定会等到所有子进程都over才结束
8.上面说主进程一定会等到子进程结束之后在over,但执行子进程的时候主进程也会往下走,两个进程并发。
但也可以采取一种方式,让主进程等待子进程结束之后再继续往下走,而在等待的这样状态被称为堵塞。
实现上述方法使用join()类方法来实现,也可以用terminate来直接over子进程
用刚刚创建进程对象.join()
9.创建子进程也可以通过创建一个类,让类继承Process,而你创建的这个类就具有创建子进程的功能了。创建之后
因为在Process类中有run方法,可以在该类中重写run方法,以便于你在执行子进程时仍然可以直接调用start方法,
Start方法里调用run方法。
而通过子类创建子进程的好处就在于你可以将很多你想实现的功能封装到类中。
10.进程池
* 进程池是创建进程的另外一种方式。与前面两种方式相比,创建更加方便,效率也更高效。
* 首先从multiprocess模块导入 Pool类,然后通过实例化类pool = Pool(3)即可创建三个进程。
* 接下来就可以定义一个函数,里面写上让进程执行的代码。然后通过for i in 循环,创建多个任务
* 通过调用pool.apply_saync(执行函数名,参数)来启动进程,与之前的start类似。
* 执行的模式是按照循环的顺序将不同任务以此放到进程池中,执行完一个任务换下一个,直到将所有任务执行完。
* 最后通过pool.close()关闭进程池,让其不能在继续添加任务。
* 与Process相比而言,进程池的主进程不会等到所有子进程结束之后在over;所以就要利用pool.join()让主进程等
* 待子进程结束之后在结束,如果没有join,会导致进程池中的任务不会执行。一般主进程只是用来等待,真正任务在子进程执行
* 进程池中数要根据压力测试,找到一个恰当的数。要根据配置 操作系统测试之后得到。
11.创建多个进程能保证使多个任务同时在执行,可具体怎么执行,谁先执行谁后执行是由操作系统里面的调度算法决定的。
也就是在真正执行的的时候不会想理论那样同时肩并肩往下走,因为受到硬件的限制。
12.进程中的堵塞与非堵塞
在多个进程执行过程中,当一个进程等待某个条件满足才能执行叫做堵塞。同理,如果多个进程同时走,不用等,非堵塞。
通常情况下,在使用进程池,仅仅利用join让主进程等一会儿,而不会让其他子进程等子进程,这样失去了意义。
13.进程与进程之间的通信
* 进程之间为什么要通信,原因就在于进程之间的变量数据本身是不共享的。但很多时候我们使用多个进程同时在做一件事,
比如获取数据,获取之后处理数据,然后保存数据,这需要三个进程来实现,速度会更快。
而要想实现通信可以使用Queue队列来实现,也是从mulitprocess模块到处,使用队列原因在于队列是先进先出
一个进程用put、一个进程用get,发送一个接受一个,发送一个接受一个,相当于一个桥梁来保证正常程序的运转
用Process配合write,read函数与Queue实现进程间通信。
具体实现方法让write(q) 接受参数,往队列里面加数据
然后read(q)同样接收参数,然后从队列里面出数据
而主线程通过join来控制,两个进程执行的先后顺序。
------------------------------------------线程----------------------------------------------------------
1. 进程线程的区别联系:
联系:都是为了实现多任务;都是自己执行自己的,多个线程执行同一个函数,各自之间相互不影响
区别:进程是资源分配的单位;线程是CPU调度的单位。
正如之前讲到,创建多个进程之后,CPU先执行哪个主子进程也是不确定的,由操作系统的调度算法决定的。
且CPU在执行的过程中通过一个箭头,箭头指向谁就执行谁。
而线程相当于在一个进程中创建两个箭头,两个箭头同时往下走,相当与同时在一个进程中实现多个任务。
使用线程的好处在于省却了进程间相互通信麻烦。
2.主进程或主线程一般不会先结束,就是要等子进程或子线程执行之后收回内存空间,当子进程执行完,等待被回收的状态叫
僵尸进程。如果主先over,那么由其产生的子进程要孤儿进程,这个时候会被PID为1的进程回收。
3.全局变量
线程共享全局变量,也就是说你在一个线程修改了全局变量,可以被另一个线程引用,多进程不共享。
所以多进程在数据共享时就需要进程之间通信,很费劲,线程不需要。
而进程的弊端,就是如果不能控制好全局变量在多个线程中执行顺序,由CPU调度轮转以后也许得不到你想要的结果。
4.当两个线程同时修改一个全局变量时,由CPU调度轮转,可能会导致一个还没修改完成,就执行了下一个了,这样的现象
叫两个线程争抢资源。为了解决这个现象,我们们可以加个条件判断,保证一个进程执行完在执行另一个进程。
但最好的方式是采用互斥锁。
5.互斥锁
为了解决多线程争抢资源。通过导入Lock类,然后实例化之后,通过acquire()进行上锁,通过release()进行解锁来解决
这个问题。原则是对两个线程同时上锁,无论哪个被先上锁,另一方都会被堵塞,直到锁被打开。
锁被打开之后,被堵塞的线程争抢上锁,然后执行各自的代码。
6.加锁的原则,加锁的代码尽量少,保证稳定运行即可。加的越多单任务越明显
9.等待解锁方式:轮询与通知
10.非共享变量,多线程中全局变量是共享的,而函数里面的变量不共享的。也就是说两个线程同时执行一个函数,
参数改变不会相互影响。
11.堵塞非堵塞、同步与异步
同步与异步:多方协同一起协同执行到底是谁先执行,确定顺序就是同步,不确定就是异步。
利用锁将多个线程按照顺序走就是同步
堵塞非堵塞:程序执行时等一个程序执行完在执行叫堵塞,如果不等叫非堵塞
12.生产者与消费者设计模式
多线程开发过程中,很可能产生数据一方的产出率,与处理数据乙方的消费率有差异,而会导致一方等待另外一方的状态,
导致耦合性很强,整体效率很低。为了解决这一类问题,引入生产与消费者模式。
而要想解决这一问题,就必须解决其强耦合性,也就是一方的速度不受另一方的影响,这个时候我们就可以利用队列
数据结构的特性。在生产者与消费者之间建一个桥梁,来保证数据时时的在两个线程间相互传输
这样的思想与进程间通信十分类似的。
13.另外扩充的认知
实现上述的过程可以通过一台电脑实现,即在同样一台电脑上使用多线程。但当用户访问数量过多,单个服务器过载,
就必须使用多个服务器,也就是多找几个计算机,让每个计算机做不同的事儿,利用计算机之间的相互通信,将数据在
服务器之间来回传输。(这个也叫分布存储、服务器集群,引入分布式集群的概念,待验证。)
14.什么是异步,如何利用异步
可以这么理解,一个进程在做一件事时,收到了另外一件任务的请求,而放下了当前任务的请求去执行另外一件任务
将另外一件任务执行完毕之后再会来执行自己的任务
异步的好处在于:实现没规定好顺序,不用一直处于等待的状态,而是先执行自己的任务,收到请求之后再去执行其他
网络上用的比较多。
用进程池实现异步
pool.apply_async(func = f1,callback=f2)
进程池创建两个进程,主进程先让子进程执行f1,然后主进程可以执行其他,当f1执行完毕之后,
父进程再去执行f2
15.在网上档下来程序先用cat read.me查看说明
16.GIL全局解释器锁题ss
Python语言中使用多线程并没有使用多进程的效率高,原因就在于Python语言的多线程有GIL锁。
在python虚拟机中,尽管可以运行多个线程,但是任意时刻只能有一个线程会被解释器执行。就像单核CPU运行多个进程一样。
而全局解释器锁就是保证同一时刻只有一个线程在运行。
那么如何解决GIL问题呢?那就是把关键的程序换成C语言来写,然后在py中调用C语言库,让一个线程执行C语言里的程序
GIL对C语言的程序是起不到任何作用的,所以多线程依然可以管用,也印证了Py是胶水语言
17.学习多进程与多线程之后,我们现在引入协程。
协程与多进程多线程一样,都可以实现多任务。但既然功能相同,为什么还要引入协程,接下来说一说他的优点。
这就回到了我们之前学过的CPU在执行多任务采用的是轮询原则。即,即便是多进程,多核CPU并发时,也不可能保证
每个任务同时执行,而是采用不断快速切换不同任务,让你觉得在同时执行,而切换的次数越多,成本越大,即每一次在
执行完之后,都要用一段空间来保存上一次执行的信息,以便下次执行时知道从哪开始继续执行。
而使用协成就会避免切换成本,原因是协程可以看作微线程,也就是将线程里执行的任务切换成很多个子任务,然后在线程
里不断切换子任务,进而实现多任务。而这样一来只是在一个任务内切换,不会进行CPU切换,这是它的好处。
而具体实现方式的话就可以用到之前我们讲的生成器了
greelet+swich可以实现协程。yiet next
18.用threading.local()创建一个对象后,在线程里赋值时可以使用它,目的是将所赋的值与线程绑定,最终可以实现不同线程拥有自己的变量。
19、ThreadLocal有什么用
简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,
自然就没有线程安全方面的问题了
20.什么是线程安全
又是一个理论的问题,各式各样的答案有很多,我给出一个个人认为解释地最好的:如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,
那么你的代码就是线程安全的
21.什么时候用多线程什么时候用多进程
计算密集型--->需要大量CPU资源,大量的计算。用多进程,因为py多线程之占用一个核,所以不能起到作用。
io密集型----->需要网络功能,大量的时间都在等待网络数据的到来(下载),大部分时间都在等待。一个核能完成,使用多线程,但有切换成本,最好使用协程。