一、 进程和线程的比较
1. 进程的开销比线程的开销大很多
2. 进程之间的数据是隔离的,但是,线程之间的数据不隔离
3. 多个进程之间的线程数据不共享----->还是让进程通信(IPC)------->进程下的线程也通信了---->队列
二、 GIL全局解释器锁(重要理论)
1.背景信息
1. Python代码运行在解释器上嘛,有解释器来执行或者解释
2. Python解释器的种类:
1、CPython 2、IPython 3、PyPy 4、Jython 5、IronPython
3. 当前市场使用的最多(95%)的解释器就是CPython解释器
4. GIL全局解释器锁是存在于CPython中
5. 结论是同一时刻只有一个线程在执行? 想避免的问题是,出现多个线程抢夺资源的情况
比如:现在起一个线程,来回收垃圾数据,回收a=1这个变量,另外一个线程也要使用这个变量a,当垃圾回收线程还没没有把变量a回收完毕,另一个线程就来抢夺这个变量a使用。
怎么避免的这个问题,那就是在Python这门语言设计之处,就直接在解释器上添加了一把锁,这把锁就是为了让统一时刻只有一个线程在执行,言外之意就是哪个线程想执行,就必须先拿到这把锁(GIL), 只有等到这个线程把GIL锁释放掉,别的线程才能拿到,然后具备了执行权限.
结论: GIL锁就是保证在统一时刻只有一个线程执行,所有的线程必须拿到GIL锁才有执行权限
2.需要重点记忆
1. python有GIL锁的原因,同一个进程下多个线程实际上同一时刻,只有一个线程在执行
2. 只有在python上开进程用的多,其他语言一般不开多进程,只开多线程就够了
3. cpython解释器开多线程不能利用多核优势,只有开多进程才能利用多核优势,其他语言不存在这个问题
4. 8核cpu电脑,充分利用起我这个8核,至少起8个线程,8条线程全是计算--->计算机cpu使用率是100%,
5. 如果不存在GIL锁,一个进程下,开启8个线程,它就能够充分利用cpu资源,跑满cpu
6. cpython解释器中好多代码,模块都是基于GIL锁机制写起来的,改不了了---》我们不能有8个核,但我现在只能用1核,----》开启多进程---》每个进程下开启的线程,可以被多个cpu调度执行
7. cpython解释器:io密集型使用多线程,计算密集型使用多进程注:1.-io密集型,遇到io操作会切换cpu,假设你开了8个线程,8个线程都有io操作---》io操作不消耗cpu---》一段时间内看上去,其实8个线程都执行了, 选多线程好一些
2. -计算密集型,消耗cpu,如果开了8个线程,第一个线程会一直占着cpu,而不会调度到其他线程执行,其他7个线程根本没执行,所以我们开8个进程,每个进程有一个线程,8个进程下的线程会被8个cpu执行,从而效率高
3. 计算密集型选多进程好一些,在其他语言中,都是选择多线程,而不选择多进程。
三、 互斥锁
3.1 概念
Python编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。在Python中我们使用threading模块提供的Lock类。锁的意义,就是只允许一个线程对数据进行更改。·
3.2 代码
1.在多线程的情况下,同时执行一个数据,会发生数据错乱的问题
n = 10 from threading import Lock import time def task(lock): lock.acquire() global n temp = n time.sleep(0.5) n = temp - 1 lock.release()
2. 拿时间换空间,空间换时间 时间复杂度
from threading import Thread if __name__ == '__main__': tt = [] lock=Lock() for i in range(10): t = Thread(target=task, args=(lock, )) t.start() tt.append(t) for j in tt: j.join() print("主", n)
四、 线程队列(线程里使用队列)
4.1 为什么线程中还有使用队列?
同一个进程下多个线程数据是共享的,队列是
管道 + 锁。所以用队列还是为了保证数据的安全
4.2 线程队列
1. 先进先出
2. 后进先出
3. 优先级的队列import queue queue.Queue()
注:queue.Queue 的缺点是它的实现涉及到多个锁和条件变量,因此可能会影响性能和内存效率。
import queue q=queue.Queue() # 无限大、 q.put('first') q.put('second') q.put('third') q.put('third') q.put('third') q.put('third') q.put('third') print(q.get()) print(q.get()) print(q.get()) ## 后进先出 import queue # Lifo:last in first out q=queue.LifoQueue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get())
优先级队列
import queue q=queue.PriorityQueue() #put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高 q.put((20,'a')) q.put((10,'b')) q.put((30,'c')) print(q.get()) print(q.get()) print(q.get()) ''' 结果(数字越小优先级越高,优先级高的优先出队): (10, 'b') (20, 'a') (30, 'c') '''
五、进程池和线程池的使用
5.1 进程池和线程池定义
进程池:提前定义好一个池子,然后,往这个池子里面添加进程,以后,只需要往这个进程池里面丢任务就行了,然后,有这个进程池里面的任意一个进程来执行任务。
线程池:提前定义好一个池子,然后,往这个池子里面添加线程,以后,只需要往这个线程池里面丢任务就行了,然后,有这个线程池里面的任意一个线程来执行任务。
5.2 进程池和线程池有什么好处呢?
六、 多线程爬取网页
import requests def get_page(url): res=requests.get(url) name=url.rsplit('/')[-1]+'.html' return {'name':name,'text':res.content} def call_back(fut): print(fut.result()['name']) with open(fut.result()['name'],'wb') as f: f.write(fut.result()['text']) if __name__ == '__main__': pool=ThreadPoolExecutor(2) urls=['http://www.baidu.com','http://www.cnblogs.com','http://www.taobao.com'] for url in urls: pool.submit(get_page,url).add_done_callback(call_back)
七、 协程理论
7.1 进程、线程、协程
进程:资源分配
线程:执行的最小单位
协程:只是程序员自己想的出来的,并不存在操作系统中。
并发:切换+保存状态
7.2 协程实现高并发
服务端:
from gevent import monkey; monkey.patch_all() import gevent from socket import socket # from multiprocessing import Process from threading import Thread def talk(conn): while True: try: data = conn.recv(1024) if len(data) == 0: break print(data) conn.send(data.upper()) except Exception as e: print(e) conn.close() def server(ip, port): server = socket() server.bind((ip, port)) server.listen(5) while True: conn, addr = server.accept() # t=Process(target=talk,args=(conn,)) # t=Thread(target=talk,args=(conn,)) # t.start() gevent.spawn(talk, conn) if __name__ == '__main__': g1 = gevent.spawn(server, '127.0.0.1', 8080) g1.join()
客户端:
import socket from threading import current_thread, Thread def socket_client(): cli = socket.socket() cli.connect(('127.0.0.1', 8080)) while True: ss = '%s say hello' % current_thread().getName() cli.send(ss.encode('utf-8')) data = cli.recv(1024) print(data) for i in range(5000): t = Thread(target=socket_client) t.start()