python线程
目录
注:线程和进程在方法大致相同
一.IPC机制
什么是IPC机制:
1.主进程与子进程进行数据交互
2.两个子进程之间的数据交互
1.主进程与子进程进行数据交互
from multiprocessing import Process, Queue
# 模块方法调用了队列方法
def producer(q):
print('子进程我准备去主进程中取值我的主队列为q', q.get())
if __name__ == '__main__':
q = Queue()
# 调用了队列的方法
p = Process(target=producer, args=(q, ))
p.start()
q.put(123)
# 主进程往队列中存放数据123
print('主进程')
"""
创建一个进程并通过队列的方法在主进程传值
通过子进程使用队列的方式进行取值
结果
主进程
子进程我准备去主进程中取值我的主队列为q 123
"""
2.两个子进程之间的数据交互
from multiprocessing import Process, Queue
def producer(q):
q.put('我是子进程1要去列队里添加值')
def consumer(q):
print('我是子进程2我要去取子进程1', q.get())
if __name__ == '__main__':
q = Queue()
w = Process(target=producer,args=(q,))
w1 = Process(target=consumer, args=(q,))
w.start()
w1.start()
print('我是主进程')
"""
同上,也可以通过两个子进程一个传入一个接纳方式进行,数据的交互
""'
二.生产者消费者模型
# 生产者
负责生产与制作数据
# 消费者
负责消费与处理数据
1.使用进程实现
from multiprocessing import Process,JoinableQueue
# JoinableQueue方法是可以算做一个队列方法.每次执行一次子进程后会给队列一个反馈信息如果
# 子进程结束后会打断循环执行主进程进行回收
import time
import random
def producer(name, food, q):
for i in range(5):
data = f'{name}制作了{food}{i}'
print(data)
time.sleep(random.randint(1, 3))
# 模拟产生过程
q.put(data)
# 将拼接好的数据传入队列中
def consumer(name, q):
while True:
food = q.get()
time.sleep(random.random())
print(f'{name}吃了{food}')
q.task_done()
# 每次去完数据必须给队列一个反馈
if __name__ == '__main__':
q = JoinableQueue()
p1 = Process(target=producer, args=('jason', '木桶饭', q))
p2 = Process(target=producer, args=('kevin', '红烧排骨', q))
c1 = Process(target=consumer, args=('qqq', q))
c2 = Process(target=consumer, args=('zzz', q))
c1.daemon = True
c2.daemon = True
# 将子进程设置成守护进程
# 那么主进程结束后子进程跟着结束
p1.start()
p2.start()
c1.start()
c2.start()
# 生产者生产完所有数据之后 往队列中添加结束的信号
p1.join()
p2.join()
# 子进程join方法将子进程结束后执行主进程
q.join()
# 运行到这一步时候就因为子进程和主进程同时结束了所以
# 等待队列中数据全部被取出(一定要让生产者全部结束才能判断正确)
三.线程
1.进程:程序应不能单独运行,只有将程序装在到内存中,系统为他分配资源才能运行
2.进程与程序的区别在于:程序是'死的'而进程是活的是根据程序的启动而进行
3.多道技术:多道技术本质是提升CPU的利用效率,多个程序加载到内存当中,在操作系统的调度下,实现并发'一并启动'
4.进程的缺陷:他在内存中创建了一个空间,内部有进程的子代码如果碰到io'其他操作的话'就会挂起 从新开始执行'如果想实现既可以进行io操作又不影响其他进程的子代码运行那么就引出了线程'在io操作下从新开始执行那么期间就会浪费CPU的资源
5.线程:因进程因为浪费资源,而多进程就更加浪费资源了
所以就开发出了 能够独立运行的基本单位-线程
eg:进程是资源分配的最小单位.线程是cpu调度的最小单位
每一个进程当中.最起码要有一个线程
6.可以这么举例子'如果把进程当做一个工厂的厂房,那么线程就是厂房内的工人,在厂房内肯定有其他公共的设施,设备或者是工具,那么这些工人(线程)就可以共享这些工具设施(资源)'
# 1.可以引出:如果两个厂房(进程)那么他 厂房与厂房之间的线程是不通用的(工人怎么能共享呢)
# 2. 一个线程只能属于一个进程
四.启动线程两种方式
"""
线程执行消耗较小,速度要快
启动线程和启动进程格式是一样的
但是线程可以不需要写__main__方法
更换了模块threading和方法Thread
"""
1.创建函数方式
import time
from threading import Thread
# 更换了模块和方法调用模块
# 定义函数 接受行参 name
def task(name):
print('%s 这个是子线程' % name)
time.sleep(2)
print('%s 子线程2号' % name)
# 创建线程
j = Thread(target=task, args=('thn',))
j.start()
print('这个是线程主线程')
"""
和进程一样先执行了主线程,在执行了子线程
结果也是不同的,因为创建线程的消耗很小
执行的时候速度超过了执行主线程的消耗
结果是
子进程 thn
这是主进程
子进程2号 thn
"""
2.创建类(面向对象)方式
import time
from threading import Thread
# 定义面向对象 创建一类继承Thread
class MyProcess(Thread):
def __init__(self, name):
super().__init__() # self.name = name
self.name = name # super().__init__()
# 唯一不同点在于这
def run(self):
print('子进程',self.name)
time.sleep(2)
print('子进程2号',self.name)
j = MyProcess('thn')
j.start()
print('这是主进程')
"""
也是和类创建进程一样,上方不同点是因为不能从新执行实例化所以需反过来调用
其他的基本一致
结果是
子进程 thn
这是主进程
子进程2号 thn
"""
五.线程join方法
1.线程join方法与进程join方法一致如下
1.线程join方法
import time
from threading import Thread
# 定义函数 接受行参 name
def task(name):
print('%s 这个是子线程' % name)
time.sleep(2)
print('%s 子线程2号' % name)
# 创建线程
j = Thread(target=task, args=('thn',))
j.start()
j.join()
print('这个是线程主进程')
"""
join方法就是要先执行子线程,子线程执行完毕后执行主线程,主线程进行回收
"""
六.同一进程内多个线程数据共享
from threading import Thread
x = 99
def task():
global x
x = 333
print(x)
# 1.本来只有在调用阶段才能 修改全局变量打印333
if __name__ == '__main__':
p = Thread(target=task)
p.start()
p.join()
print(x)
# 2.线程可以直接使用函数调方法修改了x
"""
上述也提过,进程算是一个工厂,线程就是工厂里上班的工人,内部公共工具,公共设施,等都是公用的
"""
七.线程对象属性和方法
# 1.验证主线程与子线程是在一个内存里
通过os.getpid()
# 2.验证在内存里有几个线程包含主线程与子线程
通过方法 activeCount()
# 3.获取内存里主线程名字与有子线程名字
通过方法 current_thread().name 获取主线程名字 与子线程名字
1.验证主线程与子线程是在一个内存里
def task():
print('子线程获进程号',os.getpid())
j = Thread(target=task)
j.start()
j.join()
print('这个是线程主进程',os.getpid())
"""
子线程获进程号 6096
这个是线程主进程 6096
一致的
"""
2.验证在内存里有几个线程包含主线程与子线程
from threading import Thread, active_count
import time
# 定义函数 接受行参 name
def task():
time.sleep(3)
print('子线程获进程号')
j = Thread(target=task)
j.start()
print(active_count())
"""
获取到1个子线程与1个主线程
结果是2
"""
3.获取内存里主线程名字与有子线程名字
def task():
time.sleep(3)
print('子线程获进程号',j.name) # 获取子线程名字
j = Thread(target=task)
j.start()
print(current_thread()) # 获取主线程名字
"""
<_MainThread(MainThread, started 2316)>
子线程获进程号 Thread-1
"""
八.守护线程
from threading import Thread
import time
def tack(name):
print(f'子线程{name}')
time.sleep(3)
print(f'子线程2{name}')
p = Thread(target=tack,args=('哎',))
p.daemon = True # 将子线程序设置为守护线程,主线程序接受后 子线程序立即结束
p.start()
print('主线程')
"""
结果
子进程哎
主线程
为什么没有子线程2呢 因为子线程1抢先在 主线程前跑了不然子线程1都看不到,
"""
九.GIL全局解释器锁
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:
设置 GIL;
切换到一个线程去运行;
运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));
把线程设置为睡眠状态;
解锁 GIL;
再次重复以上所有步骤。
在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL

浙公网安备 33010602011771号