社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
python
import threading # 实际上threading 是对 Thread 二次封装 使之更易用
thd = threading.Thread(target=funcName, args=(位置参数,), kwargs={key:val,...})
python
thd.start()
等待子线程结束 执行下面
join():阻塞等待某个子线程结束 —-> 多个线程则需要 多个 thd.join()
import time
import threading
def sayHello(no, age=10):
for i in range(2):
print("hell0", no)
time.sleep(1)
if __name__ == '__main__':
for i in range(3):
# 位置参数用args=元组 关键字参数用kwargs={key:val}
thd = threading.Thread(target=sayHello, args=(i,), kwargs={"age": 100})
thd.start()
# 阻塞等待子线程
thd.join()
print(6666)
threading.enumerate():查看当前线程数量 当线程数量为1时,则子线程都结束
while True:
if len(threading.enumerate()) == 1:
break
else:
time.sleep(1)
....
创建子线程的另一方式(继承threading.Thread方便代码封装)
import threading
import time
class MyThread(threading.Thread):
def run(self):
"""需要子线程执行的代码"""
for i in range(100):
print("这是子线程")
time.sleep(1)
# 创建子类对象
mt = MyThread()
# 调用子类对象.start()
mt.start() # 即一个mt子线程
for i in range(10):
print("这是主线程")
time.sleep(1)
多线程间是共享数据(因为在同一个进程中),同时修改同一数据时可能 导致 数据竞争
"""两个线程对一个全局变量g_number 数学加1"""
import threading
g_number = 0
def hello():
for i in range(1000000): # 加的次数越大越容易出现资源竞争问题
global g_number
g_number += 1
def world():
for i in range(1000000):
global g_number
g_number += 1
if __name__ == '__main__':
hello_thd = threading.Thread(target=hello)
world_thd = threading.Thread(target=world)
hello_thd.start()
world_thd.start()
# 阻塞等待
hello_thd.join()
world_thd.join()
print(g_number) # 结果随机 可能小于2000000
# 小总结: 多线程的执行顺序是随机的
# 假设某个时间点g_number = 1000.
# 线程1 对g_number所指向的内存地址中的数字(1000)加1;
# 在这个时间点上,线程2也访问到g_number所指向的内存地址中的数字(1000),然在加1;
# 这个情况下,g_number实际上只被加1
lock = threading.Lock() # 申请锁
lock.acquire() # 加锁 并阻塞,返回TorF 判断是否成功 成功往下,反之阻塞
待加锁代码块
lock.release() # 解锁 并解除阻塞
# 这个原理其实是 冻结 俩个代码块 ,释放一个才会执行第二个代码块 相当于两个车道,在特定的范围,变单车道
"""解决多线程共享数据引起竞争的问题-互斥锁"""
import threading
g_number = 0
def hello(lock):
for i in range(10):
global g_number
# 申请加锁
lock.acquire()
g_number += 1
# 释放互斥锁
# time.sleep(1)
print('hello')
lock.release()
def world(lock):
for i in range(10):
global g_number
lock.acquire()
g_number += 1
print("world")
lock.release()
if __name__ == '__main__':
# 申请一个锁
lock = threading.Lock()
# 将锁传入线程中
hello_thd = threading.Thread(target=hello,args=(lock,))
world_thd = threading.Thread(target=world,args=(lock,))
hello_thd.start()
world_thd.start()
hello_thd.join()
world_thd.join()
print(g_number)
扩展
GIL:全局解释器锁(相当于一个大的互斥锁)
c语言写的python解释器(官方)中存在,一个进程中有一个GIL来控制多线程之间资源资源,所以cpython中多线程为并发.无论多少核cpu,同一时间点,只有一个线程在运行,不能完全利用CPU(利用率永远达不到100%,多进程可以)
eg:
import threading, multiprocessing
def loop():
x = 0
while True:
x = x ^ 1
# multiprocessing.cpu_count()就是电脑的cup核数,我分配了4核(如下图)
for i in range(multiprocessing.cpu_count()):
t = threading.Thread(target=loop)
t.start()
GIL产生历史原因
龟叔编写python的年代电脑只有单核,为了实现在单核CPU上的多线程,便使用的了GIL来实现多线程,在那时这个是很了不起的,只是后来电脑拥有多核CPU是不适用了.
但是以前的代码量太多,现在在修改就很难,一直保留下来.
不推荐
,还是用官方CPython好) 推荐
)
进程的三个典型状态切换 —1.就绪态(ready);2.运行态(running);3.阻塞态(block)
实现多进程实际上就是子进程复制父进程的资源,所以资源不共享
声明开启子进程:
import multiprocessing
# 声明子进程变量
pro = multiprocessing.Process(target=funcName, args=(xx,))
pro.start() # 给操作系统发送一个创建执行子进程的信号,这个过程是需要一定的时间
pro.is_alive():判断进程是否存活
进程间的通信–队列(queue) 特点:先进先出
multiprocessing.Queue([maxsize])
import multiprocessing
import time
def func(queue):
if not queue.empty():
for i in range(queue.qsize()):
print(queue.get_nowait()) # get_nowait() ===get(True,0) 0是时间默认-1 T阻塞状态
time.sleep(1)
def main():
# 创建一个进程间通信队列
queue = multiprocessing.Queue(3)
# 声明一个进程
pro = multiprocessing.Process(target=func,args=(queue,))
# 创建一个进程并开启
pro.start()
for i in range(3):
queue.put_nowait('消息%s'%i)
if not queue.full():
queue.put_nowait("消息4") # put_nowait()===put('xxdata',True,0) 0是时间默认-1
else:
print("消息队列满了")
pro.join()
pro.terminate() # 杀死进程
a= pro.is_alive() # 判断是否还活着 有一定的延时行
print(a)
if __name__=="__main__":
main()
进程的pid pro.pid
这个是属性值;或者 os.getpid()
扩展
ps -aux
: 查看系统中所有进程信息 ps -ef
: 可以查看父级的ip(ppid) top
:动态显示进程信息 htop
:动态显示进程信息,并显示系统资源使用情况进程池概论
提前创建一定数量的进程,任务数多于进程池最大进程数量时,就等待某个进程结束,来运行下个任务;重复使用这些进程
特点:
节省重复创建进程的时间以及销毁的系统开销
自动会进程池中的进程进行管理
提高了对用户需求的响应效果
工作进程 — 就是进程池中的进程
管理进程 — 就是主进程,维护进程池,也叫控制进程
import multiprocessing
def funct_xxx(xx):
print(xx)
if __name__ == '__main__':
# 1,创建一个进程池
pool = multiprocessing.Pool(3) # 3代表最大进程数同时运行 ****但是一开始就已创建3个工作进程 加上主进程就是4个 不写就是系统最大的进程数 和CPU核数有关
# 2,创建进程池中的通信
queue = multiprocessing.Manager().Queue(4) # 用法和上面一致
# 3,添加任务
pool.apply(func=funct_xxx,args=(1,)) # 添加任务并且阻塞等待任务执行完成 能保证顺序
pool.apply_async(func=funct_xxx,args=(2,)) # 异步添加任务 不阻塞等待任务执行完成 ***用的多
# 4,关闭进程池
pool.close()
pool.join() ## 必须放在close或者terminate之后使用;
print("关闭进程池")
原理切换-用户层面实现的切换机制
通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定
在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的 切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。
import time
def a():
i = 0
while True:
print("a中", i)
i += 1
yield # 等待切换到另一个yield
time.sleep(1)
def b():
i = 0
while True:
print("b中", i)
i += 1
yield
time.sleep(1)
if __name__ == '__main__':
a1 = a()
b1 = b()
while True:
next(a1) # 唤起
next(b1) # 唤起
# a中 0
# b中 0
# a中 1
# b中 1
greenlet
为了更好使用协程来完成多任务,python中的greenlet模块对其封装(yield)
安装方式 sudo pip3 install greenlet
from greenlet import greenlet
import time
def test1():
while True:
print("---A--")
gr2.switch()
time.sleep(0.5)
def test2():
while True:
print("---B--")
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
# 切换到gr1中运行
gr1.switch()
gevent
gevent
是对greenlet
的二次封装,能够自动切换任务模块
其原理是当一个greenlet遇到IO操作(指的是input output 输入输出,比如网络、文件操作等需要耗时等待的操作)时,就自动切换到其他的greenlet,等到IO操作文采,再在合适的时候切换回来继续执行
由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO
pip3 install gevent
gevent
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(),i)
gevent.sleep(1) # 阻塞一秒
g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)
g1.join() # 等待 不让 主进程关闭
g2.join()
g3.join()
gevent 取消python耗时
from gevent import monkey
import gevent
import random
import time
# 有耗时操作时需要
monkey.patch_all() # 将程序中用到的耗时操作的代码,换为gevent中自己实现的模块
def coroutine_work(coroutine_name):
for i in range(10):
print(coroutine_name, i)
time.sleep(random.random())
gevent.joinall([
gevent.spawn(coroutine_work, "work1"),
gevent.spawn(coroutine_work, "work2")
])
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!