目录
1、进程
1.1、单进程
import time
def dance():
for i in range(5):
time.sleep(1)
print("跳舞", i)
def sing():
for i in range(5):
time.sleep(1)
print("唱歌", i)
if __name__ == '__main__':
dance()
sing()
执行结果
跳舞 0 跳舞 1 跳舞 2 跳舞 3 跳舞 4 唱歌 0 唱歌 1 唱歌 2 唱歌 3 唱歌 4
单进程,需要10秒完成
最少有一个进程 这个进程中最少有一个线程
1.2、多进程
进程:在《运行》程序就是进程,进程是分配资源的最小单位
线程:使用资源的最小单位
Process进程类的说明:
Process()
参数:
-
group:指定进程组,目前只能使用None
-
target:执行的目标任务名
- name:进程名字
- args:以元组的方式给执行任务传参
-
kwargs:以字典的方式给执行任务传参
Process创建的实例对象的常用方法:
-
start():启动子进程实例
-
join():等待子进程执行结束
-
terminate():不管任务是否完成,立即终止子进程
Process创建的实例对象的常用属性:
name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
多进程完成多任务的代码:
多进程的使用流程:
(1)导入模块(multiprocesing)
(2)创建子进程(Process)
(3)开启子进程(start)
import time
import multiprocessing
def dance():
for i in range(5):
time.sleep(1)
print("跳舞", i)
def sing():
for i in range(5):
time.sleep(1)
print("唱歌", i)
if __name__ == '__main__':
# 创建子进程
# Procrss:
# target: 指定执行的任务名(函数名)
myDance = multiprocessing.Process(target=dance)
mySing = multiprocessing.Process(target=sing)
# 开启子进程(如果不开启是不会执行子进程的)
myDance.start()
mySing.start()
多进程 需要5秒完成(提升执行的效率)
注意点!!!
有三个进程:1个主进程,2个子进程
有三个线程:每个进程里有一个线程(执行任务的)
一旦程序开启,默认就会有一个主进程,主进程里默认就会有一个主线程
1.3获取进程编号
获取进程编号的目的:验证主进程和子进程的关系,可以得知子进程是由哪个主进程创建出来的(因为子进程需要主进程回收资源)
获取进程编号的两种操作:
-
获取当前进程编号 os.getpid()
-
获取当前父进程编号 os.getppid()
获取当前进程编号:os.getpid()
单进程
import os
def dance():
print("dance: ", os.getpid())
def sing():
print("sing: ", os.getpid())
if __name__ == '__main__':
print("主进程id: ", os.getpid())
dance()
sing()
运行结果
主进程id: 51264 dance: 51264 sing: 51264 id都相同,得出只有一个进程
多进程
import multiprocessing
import os
def dance():
print("dance子进程id: ", os.getpid())
print("dance父进程id: ", os.getppid())
def sing():
print("sing子进程id: ", os.getpid())
print("sing父进程id: ", os.getppid())
if __name__ == '__main__':
print("主进程id: ", os.getpid())
myDance = multiprocessing.Process(target=dance)
mySing = multiprocessing.Process(target=sing)
myDance.start()
mySing.start()
运行结果
主进程id: 72076 dance子进程id: 21188 dance父进程id: 72076 sing子进程id: 43240 sing父进程id: 72076
1.4进程执行带有参数的任务
import os
import multiprocessing
# 带有参数的函数
def dance(count):
for i in range(count):
print("跳舞", i)
def sing(sum):
print(sum)
if __name__ == '__main__':
# args: 元组!!! (单个元素的元组要有 逗号)
# kwargs: 字典!!!(key值要和函数中的形参完全重名)
myDance = multiprocessing.Process(target=dance, args=(5,))
mySing = multiprocessing.Process(target=sing, kwargs={"sum": 5})
myDance.start()
mySing.start()
运行结果
跳舞 0 跳舞 1 跳舞 2 跳舞 3 跳舞 4 5
1.5进程的注意点(重点:进程间不共享全局变量)
进程间不共享全局变量
进程是分配资源的最小单位=====》每个进程(主进程,子进程)都会有自己独立的空间。
创建子进程会对主进程资源进行拷贝,也就是说子进程是主进程的一个副本。
之所以进程之间不共享全局变量,是因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量名字相同而已。
import multiprocessing
import time
# 全局变量列表
globalNum = []
def myWrite():
# 声明变量
global globalNum
# 向全局变量列表中写入数据
for i in range(5):
globalNum.append(i)
print("myWrite方法中的globalNum:", globalNum)
def myRead():
# 读取全局变量globalNum的值
global globalNum
print("myRead方法中的globalNum:", globalNum)
if __name__ == '__main__':
mywrite = multiprocessing.Process(target=myWrite)
myread = multiprocessing.Process(target=myRead)
mywrite.start()
time.sleep(1) # 保证数据写入全局变量globalNum中
myread.start()
运行结果
myWrite方法中的globalNum:[0, 1, 2, 3, 4] myRead方法中的globalNum:[]
1.6守护主进程
注意!!!默认主进程会等待所有的子进程结束之后在结束
import multiprocessing
import time
def func():
for i in range(5):
time.sleep(0.2)
print("func", i)
if __name__ == '__main__':
# 程序一旦运行,就会默认创建主进程
myFunc = multiprocessing.Process(target=func) # 主进程创建了子进程
myFunc.start() # 主进程开启了子进程
print("主进程over")
运行结果
主进程over func 0 func 1 func 2 func 3 func 4
-
为了保证子进程能够正常的运行,主进程会等待所有的子进程执行完以后在销毁,设置守护主进程的目的是主进程退出子进程销毁,不让主进程在等待子进程去执行
-
设置守护主进程方式:子进程对象.daemon = True
-
销毁子进程方式:子进程对象.terminate()
方式1:设置守护进程
import multiprocessing
import time
def func():
for i in range(5):
time.sleep(0.2)
print("func", i)
if __name__ == '__main__':
# 程序一旦运行,就会默认创建主进程
myFunc = multiprocessing.Process(target=func) # 主进程创建了子进程
# 方式1:设置守护进程(注意:需要在start之前设置 否则无效)
myFunc.daemon = True
myFunc.start() # 主进程开启了子进程
time.sleep(1)
print("主进程over")
方式2:手动设置
import multiprocessing
import time
def func():
for i in range(5):
time.sleep(0.2)
print("func", i)
if __name__ == '__main__':
# 程序一旦运行,就会默认创建主进程
myFunc = multiprocessing.Process(target=func) # 主进程创建了子进程
myFunc.start() # 主进程开启了子进程
time.sleep(1)
# 方式2:手动设置
myFunc.terminate()
print("主进程over")
运行结果
func 0 func 1 func 2 主进程over
进程总结:
1、多进程的使用流程:
(1)导入模块(multiprocesing)
(2)创建子进程(Process)
(3)开启子进程(start)
2、获取进程编号的两种操作:
-
获取当前进程编号 os.getpid()
-
获取当前父进程编号 os.getppid()
3、进程执行带有参数的任务
注意点:
args: 元组!!! (单个元素的元组要有 逗号)
kwargs: 字典!!!(key值要和函数中的形参完全重名)
myDance = multiprocessing.Process(target=dance, args=(5,)) mySing = multiprocessing.Process(target=sing, kwargs={"sum": 5})
4、进程间不共享全局变量
5、守护主进程
主进程默认会等待所有的子进程结束之后在结束
让子进程随着主进程的结束而结束:(两种方法)
-
守护进程(在start之前设置)
myFunc.daemon = True
-
手动设置
myFunc.terminate()
2、线程
2.1、多线程的基本使用
import threading
import time
def dance():
for i in range(5):
print("跳舞",i)
time.sleep(1)
def sing():
for i in range(5):
print("唱歌", i)
time.sleep(2)
if __name__ == '__main__':
myDance = threading.Thread(target=dance)
mySing = threading.Thread(target=sing)
myDance.start()
mySing.start()
2.2、多线程执行带有参数的任务
import threading
import time
def dance(count):
for i in range(count):
print("跳舞",i)
time.sleep(1)
def sing(count):
for i in range(count):
print("唱歌", i)
time.sleep(2)
if __name__ == '__main__':
myDance = threading.Thread(target=dance, args=(5,))
# kwargs:字典的key值必须和函数的形参完全重名才可以
mySing = threading.Thread(target=sing, kwargs={"count":5})
myDance.start()
mySing.start()
运行后默认会创建一个进程,该进程中默认有一个主线程,主线程执行代码,再创建两个子线程
即(一个进程,一个主线程,两个子线程)
2.3、线程的注意点:
- 线程之间执行是无序的
- 主线程会等待所有的子线程执行结束在结束
- 线程之间共享全局变量
- 线程之间共享全局变量数据出现错误问题
2.3.1、线程之间执行是无序的
# 线程之间是无序执行的
import threading
import time
def Func():
time.sleep(1)
print(threading.current_thread())
if __name__ == '__main__':
for i in range(5):
myFunc = threading.Thread(target=Func)
myFunc.start()
运行结果
<Thread(Thread-1 (Func), started 33340)>
<Thread(Thread-4 (Func), started 57196)><Thread(Thread-3 (Func), started 74692)>
<Thread(Thread-2 (Func), started 63812)>
<Thread(Thread-5 (Func), started 68708)>
2.3.2、主线程会等待所有的子线程执行结束在结束
解决方法:守护主线程
# 守护线程
import threading
import time
def Func():
for i in range(5):
time.sleep(0.2)
print("func")
if __name__ == '__main__':
# 让子进程随着主进程的结束而结束
# 守护线程
#(1)Thread(daemon = True)
myFunc = threading.Thread(target=Func, daemon=True)
myFunc.start()
time.sleep(0.5)
print("主线程over")
运行结果
func
func
主线程over
2.3.3、线程之间共享全局变量
# 线程之间共享全局变量
import threading
import time
globalNum = []
def myWrite():
global globalNum
for i in range(5):
globalNum.append(i)
print(globalNum)
def myRead():
global globalNum
print(globalNum)
if __name__ == '__main__':
mywrite = threading.Thread(target=myWrite)
myread = threading.Thread(target=myRead)
mywrite.start()
time.sleep(1)
myread.start()
运行结果
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]结论:
由于线程是依附于进程的,一个进程中的所有的线程都是使用同一片内存空间
一个进程中的,线程是共享全局变量的
2.3.4、线程间共享全局变量问题
import threading
globalNum = 0
def sumNum1():
global globalNum
for i in range(10000000):
globalNum += 1
print("方法sunNum1计算的结果:", globalNum)
def sumNum2():
global globalNum
for i in range(10000000):
globalNum += 1
print("方法sunNum1计算的结果:", globalNum)
if __name__ == '__main__':
sumnum1 = threading.Thread(target=sumNum1)
sumnum2 = threading.Thread(target=sumNum2)
sumnum1.start()
sumnum2.start()
运行结果
方法sunNum1计算的结果: 19120946
方法sunNum1计算的结果: 20000000其结果值不相等,线程间共享全局变量出现问题
解决线程间共享全局变量问题
- 方法1:线程等待(join)
- 方法2:互斥锁
线程等待(join):
import threading
globalNum = 0
def sumNum1():
global globalNum
for i in range(10000000):
globalNum += 1
print("方法sunNum1计算的结果:", globalNum)
def sumNum2():
global globalNum
for i in range(10000000):
globalNum += 1
print("方法sunNum1计算的结果:", globalNum)
if __name__ == '__main__':
firstThread = threading.Thread(target=sumNum1)
secondThread = threading.Thread(target=sumNum2)
firstThread.start()
# 主线程等待第一个线程执行完成以后,代码在继续执行,让其第二个线程执行
# 线程同步:一个任务执行完以后另一个任务才能执行,同一个时刻只有一个任务执行
firstThread.join() # 让主线程等待firstThread执行完毕 在继续向下运行
secondThread.start()
运行结果:
方法sunNum1计算的结果: 10000000
方法sunNum1计算的结果: 20000000
互斥锁
互斥锁:对共享数据进行锁定,保证同一时刻只能有一个线程去操作。
threading模块中定义了Lock变量,这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁。
互斥锁的使用步骤:
# 创建锁
mutex = threading.Lock()
# 上锁
mutex.acquire()
# 释放锁
mutex.release()
注意点:
- acquire和release方法之间的代码同一时刻只能有一个线程去操作
- 如果在调用acquire方法的时候,其他线程已经使用了这个互斥锁,那么此时acquire方法会堵塞,直到这个互斥锁释放后才能再次上锁。
import threading
globalNum = 0
# 创建锁
mutex = threading.Lock()
def sumNum1():
global globalNum
# 上锁
mutex.acquire()
for i in range(10000000):
globalNum += 1
# 释放锁(如果不解锁,就会产生死锁问题)
mutex.release()
print("方法sunNum1计算的结果:", globalNum)
def sumNum2():
global globalNum
# 上锁(同一把锁,必须先解锁才可以上锁)
mutex.acquire()
for i in range(10000000):
globalNum += 1
# 释放锁
mutex.release()
print("方法sunNum1计算的结果:", globalNum)
if __name__ == '__main__':
firstThread = threading.Thread(target=sumNum1)
secondThread = threading.Thread(target=sumNum2)
firstThread.start()
secondThread.start()
运行结果
方法sunNum1计算的结果: 10000000
方法sunNum1计算的结果: 20000000
死锁
import threading
# 创建锁
mutex = threading.Lock()
def sumNum1():
num = 1
# 上锁
mutex.acquire()
if num == 1:
mutex.release() # 解除死锁
return
# 释放锁
mutex.release()
def sumNum2():
# 上锁
mutex.acquire()
print("sumNum2")
# 释放锁
mutex.release()
if __name__ == '__main__':
firstThread = threading.Thread(target=sumNum1)
secondThread = threading.Thread(target=sumNum2)
firstThread.start()
secondThread.start()