汕头网站搭建多少钱,苏州网站关键词优化推广,深圳十大投资公司排名,新浪博客 搬家 wordpress 工具今日内容概要 进程和线程的比较 GIL全局解释器锁(重要理论) 互斥锁 线程队列(线程里使用队列) 进程池和线程池的用法 协程理论 如何使用协程 基于协程的高并发城程序 进程和线程比较
1.进程的开销比线程的开销大很多 2.进程之间的数据是隔离的#xff0c;但是#x…今日内容概要 进程和线程的比较 GIL全局解释器锁(重要理论) 互斥锁 线程队列(线程里使用队列) 进程池和线程池的用法 协程理论 如何使用协程 基于协程的高并发城程序 进程和线程比较
1.进程的开销比线程的开销大很多 2.进程之间的数据是隔离的但是线程之间的数据不隔离 3.多个进程之间的线程数据不共享-----还是让进程通信IPC------进程下的线程也通信了-----队列
GIL全局解释器锁
python在设计之初就考虑到要在主循环中同时只有一个线程在执行。虽然python解释器中可以“运行”多个线程但在任意时刻只有一个线程在解释器中运行
对python解释器的访问由全局解释器锁(GIL)来控制正是这个锁能保证同一时刻只有一个线程在运行
背景 1.python代码运行在解释器上由解释器来执行或解释 2.python解释器的种类CPython IPython PyPy Jython IronPython 3.当前市场使用的最多的解释器就是CPython解释器 4.GIL全局解释器锁是存在于CPython中 5.结论是同一时刻只有一个线程在执行? 想避免的问题是出现多个线程抢夺资源的情况 比如现在起一个线程来回收垃圾数据回收a1这个变量另外一个线程也要使用这个变量a当垃圾回收线程还没没有把变量a回收完毕另一个线程就来抢夺这个变量a使用。 怎么避免的这个问题那就是在Python这门语言设计之处就直接在解释器上添加了一把锁这把锁就是为了让统一时刻只有一个线程在执行言外之意就是哪个线程想执行就必须先拿到这把锁(GIL), 只有等到这个线程把GIL锁释放掉别的线程才能拿到然后具备了执行权限.
“GIL锁就是保证在同一时刻只有一个线程执行所有的线程必须拿到GIL锁才有执行权限”
记忆问题 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密集型使用多线程计算机密集型使用多进程 io密集型遇到io操作会切换cpu假设开启了8个线程8个线程都有io操作---- io操作不消耗cpu---一段时间看上去其实8个线程都执行了选多线程好一些 计算机密集型消耗cpu如果开了8个线程第一个线程会一直占着cpu而不会调度到其他线程执行其他7个线程根本没执行所以我们开8个线程每个进程有一个线程8个进程下的线程会被8个cpu执行从而效率高 计算密集型选多进程好一些在其他语言中都是选择多线程而不是多进程
互斥锁
在多线程的情况下同时执行一个数据会发生数据错乱的问题
n 10
from threading import Lock
import time
def task():global ntemp ntime.sleep(0.5)n temp - 1lock.release()from threading import Threadif __name__ __main__:tt []for i in range(10):t Thread(targettask,)t.start()tt.append(t)for j in tt:j.join()print(主,n)
# 主 9n 10
from threading import Lock
import time
def task():global ntemp ntime.sleep(0.5)n temp - 1拿时间换空间空间换时间 时间复杂度
from threading import Threadif __name__ __main__:tt []for i in range(10):t Thread(targettask, )t.start()tt.append(t)for j in tt:j.join()print(主, n)
# 主0
面试题既然有了GIL锁为什么还要互斥锁(多线程下 比如起了2个线程来执行aa1a一开始是0 1.第一个线程来了拿到0开始执行aa1这个时候结果a就是1了 2.第一个线程得到的结果1还没有赋值回去给a这个时候第二个线程来了拿到a0继续执行 aa1 3.加了互斥锁就能解决多线程下操作同一个数据发生错乱的问题
线程队列
同一个进程下多个线程数据是共享的为什么先同一个进程下还会去使用队列呢 因为队列是 管道锁 所以用队列还是为了保证数据的安全
先进先出
class queue.Queue(maxsize0)
import queueqqueue.Queue()
q.put(first)
q.put(second)
q.put(third)print(q.get())
print(q.get())
print(q.get())结果(先进先出):
first
second
third进程Queue用于父进程与子进程或同一父进程中多个子进程间数据传递
python自己的多个进程间交换数据或者与其他语言如Java进程queue就无能为力queue.Queue 的缺点是它的实现涉及到多个锁和条件变量因此可能会影响性能和内存效率。后进先出
class queue.LifoQueue(maxsize0)
import queueqqueue.LifoQueue()
q.put(first)
q.put(second)
q.put(third)print(q.get())
print(q.get())
print(q.get())结果(后进先出):
third
second
first存储数据时可设置优先级的队列
class queue.PriorityQueue(maxsize0)优先级队列
import queueqqueue.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)进程池和线程池的作用
进程池提前定义好一个池子然后往这个池子里面添加进程以后只需要往这个进程池里面丢任务就行了然后有这个进程池里面的任意一个进程来执行任务 线程池提前定义好一个池子然后往这个池子里面添加线程以后只需要往这个线程池里面丢任务就行了然后有这个线程池里面的任意一个线程来执行任务
def task(n, m):return n mfrom concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutordef callback(res):print(res.result())if __name__ __main__:pool ProcessPoolExecutor(3)pool.submit(task, m1, n2).add_done_callback(callback)pool.shutdown()print(123)
多线程爬取网页
import requestsdef get_page(url):resrequests.get(url)nameurl.rsplit(/)[-1].htmlreturn {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__:poolThreadPoolExecutor(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)
协程理论
协程是单线程下的并发又称微线程。一句话说明什么是协程协程是一种用户态的轻量级线程即协程是由用户程序自己控制调度的。
需要强调的是 1.python的线程属于内核级别的即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限切换其他线程运行 2.单线程内开启协程一旦遇到io就会从应用程序级别(而非操作系统)控制切换以此来提升效率(非io操作的切换与效率无关) 对比操作系统控制线程的切换用户在单线程内控制协程的切换
优点如下 1.协程的切换开销更小属于程序级别的切换操作系统完全感知不到因而更加轻量级 2.单线程内就可以实现并发效果最大限度利用cpu
缺点如下 1.协程的本质是单线程下无法利用多核可以是一个程序开启多个进程每个进程内开启多个线程每个线程内开启协程 2.协程指的是单个线程因而一旦协程出现阻塞将会阻塞整个线程
总结协程特点
1.必须在只有一个单线程里实现开发 2.修改共享数据不需要加锁 3.用户程序里自己保存多个控制流的上下文栈 4.附加一个协程遇到io操作自动切换到其他协程
协程实现高并发
服务端
from gevent import monkey;monkey.patch_all()
import gevent
from socket import socket
# from multiprocessing import Process
from threading import Threaddef talk(conn):while True:try:data conn.recv(1024)if len(data) 0: breakprint(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()# tProcess(targettalk,args(conn,))# tThread(targettalk,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, Threaddef 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(targetsocket_client)t.start()
猴子补丁
猴子补丁的功能一切皆对象
拥有在模块运行时替换的功能例如一个函数对象赋值给另外一个函数对象(把函数原本的执行的功能给替换了
class Monkey():def play(self):print(猴子在玩)class Dog():def play(self):print(狗子在玩)
mMonkey()
m.play()
m.playDog().play
m.play()monkey patch的应用场景
这里有一个比较实用的例子很多用到import json,后来发现ujson性能更高如果觉得把每个文件的import json改成import ujson as json成本较高或者说想测试一下ujson替换是否符合预期只需要在入口加上
import json
import ujsondef monkey_patch_json():json.__name__ ujsonjson.dumps ujson.dumpsjson.loads ujson.loads
monkey_patch_json()
aajson.dumps({name:lqz,age:19})
print(aa)
Gevent介绍
gevent是一个第三方库可以轻松通过gevent实现并发同步或异步编程在gevent中用到的主要模式是gevent它是以C扩展模块形式接入Python的轻量级协程。Greenlet全部运行在主程序操作系统进程内部但它们被协作式地调度
用法
#用法
g1gevent.spawn(func,1,,2,3,x4,y5)创建一个协程对象g1spawn括号内第一个参数是函数名如eat后面可以有多个参数可以是位置实参或关键字实参都是传给函数eat的g2gevent.spawn(func2)g1.join() #等待g1结束g2.join() #等待g2结束#或者上述两步合作一步gevent.joinall([g1,g2])g1.value#拿到func1的返回值
示例1
import gevent
def eat(name):print(%s eat 1 %name)gevent.sleep(2)print(%s eat 2 %name)def play(name):print(%s play 1 %name)gevent.sleep(1)print(%s play 2 %name)g1gevent.spawn(eat,lqz)
g2gevent.spawn(play,namelqz)
g1.join()
g2.join()
#或者gevent.joinall([g1,g2])
print(主)示例2 上例gevent.sleep(2)模拟的是gevent可以识别的io阻塞,而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面如timesocket模块之前或者我们干脆记忆成要用gevent需要将from gevent import monkey;monkey.patch_all()放到文件的开头from gevent import monkey;monkey.patch_all()import gevent
import time
def eat():print(eat food 1)time.sleep(2)print(eat food 2)def play():print(play 1)time.sleep(1)print(play 2)g1gevent.spawn(eat)
g2gevent.spawn(play_phone)
gevent.joinall([g1,g2])
print(主)# 我们可以用threading.current_thread().getName()来查看每个g1和g2查看的结果为DummyThread-n即假线程