Table of Contents
- 线程概念
- 进程与线程的区别
- 线程常用方法
- 线程常用方法
- 普通创建方式
- 重写类,来实现启动线程
- 其他方法
- GIL
- python多线程的工作流程
- 线程锁
- 互斥锁(mutex)
- 递归锁
- 信号量(BoundedSemphore)
- Event事件
- 条件(Condition类)
- 定时器
- 进程与线程的区别
- 多进程
- 什么是多进程
- 相关用法
- 进程间的通信
- 使用Queue
- 使用Pipe
- 进程池
多线程
线程概念
所谓线程是操作系统能够进行运算调度的最小单元,一个线程是一个执行上下文,是cpu执行命令时的一串指令,一个进程能够并发出多个线程,来执行不同的任务。
进程与线程的区别
1.同一进程中的线程共享同一内存空间,但进程之间是独立的。
2.同一进程中的线程共享数据,但是进程之间的数据是独立的。
3.同一进程中的线程可以很方便地通信,但是进程之间通信之间的通信则需要通过代理实现。
4.在一个进程中创建一个线程很容易,但是进程创建一个子进程,则fork一个出来,复制一份内存空间
5.主线程可以操纵同一进程中的其他线程,但是进程只能操纵它的子进程。线程启动快,进程启动慢。线程之间切换,比较耗时,比较占用cpu,比较适合io密集型任务,
而进程之间的切换比较快速,但是进程fork出很多子进程,比较占用内存空间。对主线程的修改可能会对其他线程造成影响,但是进程和进程,进程和子进程,都是一
个独立的内存空间,所以一个进程的修改,不会对其他进程造成影响。
线程常用方法
方法 | 注释 |
---|---|
start() | 线程如果已经准备,启用该方法,则开始启动线程 |
setName() | 为线程设置名称 |
join() | 逐个执行每个进程,主线程会等到子线程全部执行完毕,在退出 |
run() | 可以重写类时,重写run方法,起到自定义线程类的作用 |
setDaemon(True) | 设置为守护进程 |
线程常用方法
普通创建方式
import threading
import time
def run(n):
print("task", n)
sleep(1)
t1 = threading.Thread(target=run, args=('t1',))
t2 = threading.Thread(target=run, args=('t2',))
t3 = threading.Thread(target=run, args=('t3',))
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
重写类,来实现启动线程
class MyThread(threading.Thread):
def __init__(self, n):
super().__init__()
self.n = n
def run(self, self.n):
print("task", n)
time.sleep(1)
print("1s")
time.sleep(1)
print("2s")
if __name__ == "__main__":
t1 = MyThread("one")
t2 = MyThread("two")
t1.start()
t2.start()
其他方法
- threading.currentthread(): 输出当前线程的名称
- threading.activecount(): 输出当前活跃线程的个数
- t.setDaemon(True): 设置当前线程为守护进程,只要主进程一退出,子进程也随之退出, 必须在调用start()之前使用,否则会报RuntimeError的错误
GIL
1.GIL全称为Global Interpreter Lock(全局解释器锁),全局解释器锁的存在,保证了同一进程中,只能有一个线程正在运行,所以python多线程编程效率并没有那么高,
通过使用多进程的方式,能够有效地提升效率。python2中GIL是通过ticks来计数,计数值到达100,则解释器,就会释放,但是在python3中,GIL是通过计时来实现了,只要时间一到,不管任务完没完成,都会收回
GIL(这种情况下,对cpu密集型运算更有利了一些,但依然问题很大),这种情况下,python对cpu密集型任务并不友好,而对io密集型任务比较友好。
2.GIL在cpython才存在,jpython和pypy中是没用GIL,因为cpython调用的是c语言的原生线程
python多线程的工作流程
ipython使用多线程的时候,是使用c语言原生线程
1.拿到公共数据
2.申请GIL
3.python解释器调用os原生线程
4.os执行cpu运算
5.时间到了收回GIL,无论是否完成任务
6.如此循环以上过程
7.当其他线程完成后,就继续执行之前的线程(从之前线程执行的上下文处开始执行),每个线程都执行自己的运算,时间到了就切换线程
线程锁
因为线程之间是公用同一份数据,所以当多个线程对同一数据进行操作时,容易产生脏数据,导致数据出现错误,所以出现了线程锁的概念,在一个线程拥有该锁时,其他线程不能进行操作,保证了,同一数据在同一时刻,
只有一个线程对其进行操作
互斥锁(mutex)
为了防止上述情况的发生,所以有了互斥锁
import threading
# 实例化了一个锁对象
lock = threading.Lock()
def run():
lock.acquire() # 获得锁,其他线程不能操作
print("hello world")
lock.release() # 释放锁,其他线程开始竞争
递归锁
与上面的Lock用法类似,RLock可以嵌套,实现多个锁
import threading
rlock = threading.RLock()
def run():
rlock.acquire()
rlock.acquire()
print("hello world")
rlock.release()
print("hello syk")
rlock.release()
信号量(BoundedSemphore)
互斥锁只允许一个线程修改数据,信号量则允许多个线程同时修改,就比如厕所有三个坑,只有其中的坑空出来了,才能有人进去
import threading
semphore = threading.BoundedSemphore(5) # 表示最多有五个线程同时进行操作
def run(i):
semphore.acquire()
print("hello",i)
for i in range(20):
t = threading.Thread(target=run)
t.start()
while t.active_count != 1:
pass
else:
print("all thread has been done")
Event事件
python线程的事件主要是用于主线程控制其他线程的执行,event是一个简单的事件同步对象,提供了一下几种方法:
方法 | 注释 |
---|---|
set() | 将flag置为True |
clear() | 将flag置为False |
isset() | 是否将flag设置为True |
wait() | 一直监听flag,如果没有监听到,则一直为阻塞状态 |
条件(Condition类)
使得线程等待,满足条件才将n个线程释放
定时器
定时器,指定n秒后执行操作
import threading
def hello():
print("hello world")
t = threading.Timer(1, hello)
t.start()
多进程
什么是多进程
1.一个程序的执行实例就是一个进程,进程提供一个程序运行所需要的所有资源(进程本质上是资源的集合)
2.每一个进程都有自己的虚拟地址空间,可执行的代码块,操作系统接口,安全的上下文(记录启动该进程的用户和权限),还有唯一的进程ID,
环境变量,优先级,最大最小的工作空间(内存空间)。
与进程相关的资源:
1.内存页(统一进程下的线程共享同一内存空间)
2.文件描述符
3.安全凭证(启动该进程的用户ID)
相关用法
from multiprocessing import Process
import time
def hello():
print("helo world")
if __name__ == "__main__":
p = Process(target=hello)
p.start()
p.join()
进程间的通信
由于进程间的数据是独立的,所以进程间的通信可以通过Queue和Pipe来实现
使用Queue
from multiprocessing import Process, Queue
def q(p):
p.put([41, None, 'hello'])
if __name__ == "__main__":
p = Queue()
t = Process(target=q, args=(p,))
t.start()
print(q.get())
t.join()
使用Pipe
Pipe的实质是数据的传递而不是数据的共享,在管道的两头都有一个recv和send方法,负责接受和发送,
但是如果两端同时进行相同的操作,那么就会发生错误,破坏管道中的数据。
from multiprocessing import Process, Pipe
import time
def parent_pro(conn):
conn.send("hello world, my son")
conn.close()
def child_pro(conn):
data = conn.recv()
print(data)
conn.close()
if __name__ == "__main__":
parent_pipe, child_pipe = Pipe()
p1 = Process(target=parent_pro, args=(parent_pipe,))
p2 = Process(target=child_pro, args=(child_pipe,))
p1.start()
p2.start()
p1.join()
p2.join()
进程池
因为多进程比较消耗内存空间,所以在使用多进程时,一般可以使用进程池(用来防止内存被大量消耗), (多线程不需要这种概念,多线程的启动消耗比较小,但是多线程切换比较消耗cpu资源)
常用方法:
1.apply(): 同步执行(串行)
2.applyasync(): 异步执行(并行)
3.terminate(): 立即关闭进程池
4.join(): 主进程等待所有子进程执行完毕,必须在close()和terminate()之后
5.close(): 待所有进程执行完毕之后,关闭进程池