多线程和多进程
一、前言
线程是操作系统能够进行运算调度的最小单位,我们可以创建多个线程做不同的事,比如一个线程进行数据的发送,而另一个进行数据的接收。但Python并不是真正多核运行的多线程机制,所以有时候要充分地使用多核CPU的资源,我们还要使用到多进程
二、多线程
1、多线程的定义和启动
多线程就是我们能够同时执行不同的程序,比如上一章讲到的socket,我们可以一边发送,一边进行接收
import socket
import threading
import time
def recv_data():
while True:
try:
print("start recv")
data, addr = s.recvfrom(1024)
print("recv message is {}".format(data))
except:
# 1s没有收到数据,会跳转到这里
print("No data recv")
# 死循环,记住要带循环,不然会占满CPU
time.sleep(1)
def send_data():
while True:
# 死循环,1s发送一次
print("send data")
s.sendto("Hello".encode(), ("192.168.3.23", 8888))
time.sleep(1)
if __name__ == '__main__':
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("192.168.3.24", 8888))
# 设置接收超时时间,若1s没有收到数据,则抛出异常
s.settimeout(1)
# 定义接收线程,目标函数为recv_data,并启动
threading.Thread(target=recv_data).start()
# 定义发送线程,目标函数为send_data,并启动
threading.Thread(target=send_data).start()
结果: start recv
send data
No data recv
send data
start recv
send data
No data recv
send data
...
...
...
2、线程的的结束和状态
我们启动了两个线程,一个循环发送,一个循环接收,然后结果就会循环打印两个线程中的内容。线程的结束有两种方式,一是线程中的代码自然结束,二是线程中的代码抛出了异常,我们可以使用is_Alive方法判断当前线程是否存活
import socket
import threading
import time
def recv_data():
while True:
try:
print("start recv")
data, addr = s.recvfrom(1024)
print("recv message is {}".format(data))
except:
# 1s没有收到数据,会跳转到这里
print("No data recv")
# 死循环,记住要带循环,不然会占满CPU
time.sleep(1)
def send_data():
while True:
# 死循环,1s发送一次
print("send data")
s.sendto("Hello".encode(), ("192.168.3.23", 8888))
time.sleep(1)
if __name__ == '__main__':
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("192.168.3.24", 8888))
# 设置接收超时时间,若1s没有收到数据,则抛出异常
s.settimeout(1)
# 定义接收线程t1,目标函数为recv_data,但不启动
t1 = threading.Thread(target=recv_data)
# 定义发送线程t2,目标函数为send_data
t2 = threading.Thread(target=send_data)
# 启动t2线程
t2.start()
# 显示t1线程当前的状态,由于没有启动,所以返回False
print("t1 thread status {}".format(t1.isAlive()))
# 显示t2线程当前的状态,由于已经启动,所以返回True
print("t2 thread status {}".format(t2.isAlive()))
结果: send data
t1 thread status False
t2 thread status True
send data
send data
send data
...
...
...
3、线程的传参
刚才我们是只是启动了函数,我们也可以在启动的时候给函数里面传递参数,方法如下:
import socket
import threading
import time
def recv_data():
while True:
try:
print("start recv")
data, addr = s.recvfrom(1024)
print("recv message is {}".format(data))
except:
# 1s没有收到数据,会跳转到这里
print("No data recv")
# 死循环,记住要带循环,不然会占满CPU
time.sleep(1)
def send_data(send_data):
while True:
# 死循环,1s发送一次
print("send data messgae is {}".format(send_data))
s.sendto(send_data.encode(), ("192.168.3.23", 8888))
time.sleep(1)
if __name__ == '__main__':
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("192.168.3.24", 8888))
# 设置接收超时时间,若1s没有收到数据,则抛出异常
s.settimeout(1)
# 定义接收线程t1,目标函数为recv_data,但不启动
t1 = threading.Thread(target=recv_data)
# 定义发送线程t2,目标函数为send_data,参数args的格式为元组,所以不管几个参数,都要用(),并且最 后要用逗号分隔
t2 = threading.Thread(target=send_data, args=("Hello", ))
# 启动t2线程
t2.start()
# 显示t1线程当前的状态,由于没有启动,所以返回False
print("t1 thread status {}".format(t1.isAlive()))
# 显示t2线程当前的状态,由于已经启动,所以返回True
print("t2 thread status {}".format(t2.isAlive()))
结果: send data messgae is Hello
t1 thread status False
t2 thread status True
send data messgae is Hello
send data messgae is Hello
send data messgae is Hello
...
...
...
4、线程的同步,锁的机制
我们有时候多个线程可能同时对同一个变量进行操作,这样的话,就会对这个变量造成意想不到的结果,所以我们在这里介绍锁的机制,即同一时刻,只能有一个线程对变量进行操作
import threading
import time
# 定义锁
lock = threading.Lock()
def Thread1():
global num
while True:
print("Thread1 wait")
# 获取锁,未释放锁之前,其他线程无法对变量进行操作。若锁已经被其他线程获取,在在此阻塞等待
lock.acquire()
print("Thread1 get lock")
time.sleep(5)
# 使用完成后,释放锁
lock.release()
def Thread2():
global num
while True:
print("Thread2 wait")
# 获取锁,未释放锁之前,其他线程无法对变量进行操作。若锁已经被其他线程获取,在在此阻塞等待
lock.acquire()
print("Thread2 get lock")
time.sleep(3)
# 使用完成后,释放锁
lock.release()
if __name__ == '__main__':
# 定义接收线程t1,目标函数为Thread1
t1 = threading.Thread(target=Thread1)
# 启动线程t1线程
t1.start()
# 定义发送线程t2,目标函数为Thread2
t2 = threading.Thread(target=Thread2)
# 启动t2线程
t2.start()
结果: Thread1 wait
Thread1 get lock
Thread2 wait
Thread1 wait
Thread2 get lock
Thread2 wait
Thread1 get lock
Thread1 wait
Thread2 get lock
三、多进程
进程和线程的区别在于,进程要独占一个CPU的PID
1、多进程的定义和创建
import socket
import time
from multiprocessing import Process
def recv_data():
while True:
try:
print("start recv")
data, addr = s.recvfrom(1024)
print("recv message is {}".format(data))
except :
# 1s没有收到数据,会跳转到这里
print("No data recv")
# 死循环,记住要带循环,不然会占满CPU
time.sleep(1)
def send_data():
while True:
# 死循环,1s发送一次
print("send data")
s.sendto("Hello".encode(), ("192.168.3.23", 8888))
time.sleep(1)
if __name__ == '__main__':
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("192.168.3.24", 8888))
# 设置接收超时时间,若1s没有收到数据,则抛出异常
s.settimeout(1)
# 定义接收进程,目标函数为recv_data,并启动
Process(target=recv_data).start()
# 定义发送进程,目标函数为send_data,并启动
Process(target=send_data).start()
结果: start recv
name 's' is not defined
No data recv
send data
Process Process-2:
Traceback (most recent call last):
File"C:\Users\Donnie\AppData\Local\Programs\Python\Python36\lib\multiprocessing\process.py", line 258, in _bootstrap
self.run()
File "C:\Users\Donnie\AppData\Local\Programs\Python\Python36\lib\multiprocessing\process.py", line 93, in run
self._target(*self._args, **self._kwargs)
File "F:\Test\hello.py", line 24, in send_data
s.sendto("Hello".encode(), ("192.168.3.23", 8888))
NameError: name 's' is not defined
我们使用跟线程定义一样方法的代码,把线程的定义换成进程的定义并启动,发现,报错了。。。这是因为进程没有任何共享状态,进程修改的数据,改动仅限于该进程内,所以在主进程中定义的变量并不能被传递到其他进程当中
我们将套接字改成全局变量
import socket
import time
from multiprocessing import Process
# 全局初始化套接字对象
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def recv_data():
while True:
try:
print("start recv")
data, addr = s.recvfrom(1024)
print("recv message is {}".format(data))
except :
# 1s没有收到数据,会跳转到这里
print("No data recv")
# 死循环,记住要带循环,不然会占满CPU
time.sleep(1)
def send_data():
while True:
# 死循环,1s发送一次
print("send data")
s.sendto("Hello".encode(), ("192.168.3.23", 8888))
time.sleep(1)
if __name__ == '__main__':
s.bind(("192.168.3.24", 8888))
# 设置接收超时时间,若1s没有收到数据,则抛出异常
s.settimeout(1)
# 定义接收进程,目标函数为recv_data,并启动
Process(target=recv_data).start()
# 定义发送进程,目标函数为send_data,并启动
Process(target=send_data).start()
结果: send data
start recv
No data recv
start recv
send data
No data recv
start recv
...
...
...
2、join方法
进程有join方法,意思就是等待当前进程执行完成后,主进程再继续往下进行
import socket
import time
from multiprocessing import Process
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def send_data():
for i in range(0, 3):
# 死循环,1s发送一次
print("send data")
s.sendto("Hello".encode(), ("192.168.3.23", 8888))
time.sleep(1)
if __name__ == '__main__':
s.bind(("192.168.3.24", 8888))
# 设置接收超时时间,若1s没有收到数据,则抛出异常
s.settimeout(1)
# 定义发送进程,目标函数为send_data
p = Process(target=send_data)
# 启动进程
p.start()
# join方法,等待p进程执行完成
p.join()
print("主进程")
结果: send data
send data
send data
主进程
3、守护进程
我们可以设置守护进程,即当主进程结束后,设置后守护进程的进程随机也会结束
import socket
import time
from multiprocessing import Process
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def send_data():
for i in range(0, 3):
# 死循环,1s发送一次
print("send data")
s.sendto("Hello".encode(), ("192.168.3.23", 8888))
time.sleep(1)
if __name__ == '__main__':
s.bind(("192.168.3.24", 8888))
# 设置接收超时时间,若1s没有收到数据,则抛出异常
s.settimeout(1)
# 定义发送进程,目标函数为send_data
p = Process(target=send_data)
# 设置为守护进程
p.daemon = True
# 启动进程
p.start()
# 主进程结束,p进程也随即结束
print("主进程")
结果: 主进程
4、多进程同步和锁
同线程一样,进程间也会牵扯到锁的情况,只不过用法稍有不同
from multiprocessing import Process, Lock
import time
def Process1(lock):
global num
while True:
print("Process1 wait")
# 获取锁,未释放锁之前,其他进程无法对变量进行操作。若锁已经被其他线程获取,在在此阻塞等待
lock.acquire()
print("Process1 get lock")
time.sleep(5)
# 使用完成后,释放锁
lock.release()
def Process2(lock):
global num
while True:
print("Process2 wait")
# 获取锁,未释放锁之前,其他进程无法对变量进行操作。若锁已经被其他线程获取,在在此阻塞等待
lock.acquire()
print("Process2 get lock")
time.sleep(3)
# 使用完成后,释放锁
lock.release()
if __name__ == '__main__':
# 定义锁
lock = Lock()
# 定义接收进程p1,目标函数为Process1
p1 = Process(target=Process1, args=(lock, ))
# 启动p1进程
p1.start()
# 定义接收进程p2,目标函数为Process2
p2 = Process(target=Process2, args=(lock, ))
# 启动p2进程
p2.start()
结果: Process1 wait
Process1 get lock
Process2 wait
Process1 wait
Process2 get lock
Process2 wait
Process1 get lock
Process1 wait
Process2 get lock
...
...
...
...
5、进程间通信
我们刚提过,进程修改的数据,改动仅限于该进程内,那我们可以通过通信的方式来使得不同的进程之间实现通信常见的通信有两种,两个进程之间使用管道,多个进程之间使用队列
5.1 管道
from multiprocessing import Process, Pipe
import time
def Process1(pipe):
# 等待接收管道数据
data = pipe.recv()
print("Process1 recv message is {}".format(data))
def Process2(pipe):
# 通过管道发送数据
pipe.send("This is Process2")
if __name__ == '__main__':
# 定义管道,Pipe方法返回(conn1,conn2)代表一个管道的两个端,Pipe方法有duplex参数,默认为True,即全双工模式,两个进程聚能收发
pipe = Pipe()
# 定义进程p1,目标函数为Process1,参数为管道的一端
p1 = Process(target=Process1, args=(pipe[0], ))
# 启动p1进程
p1.start()
# 定义进程p2,目标函数为Process2,参数为管道的另一端
p2 = Process(target=Process2, args=(pipe[1], ))
# 启动p2进程
p2.start()
结果: Process1 recv message is This is Process2
5.2 队列
from multiprocessing import Process, Queue
import time
def Process1(q):
while True:
# 循环从队列中获取数据
data = q.get()
print("Process1 recv message is {}".format(data))
def Process2(q):
while True:
# 循环从队列中获取数据
data = q.get()
print("Process2 recv message is {}".format(data))
def Process3(q):
# 往队列里面放入0-9十个数
for i in range(0, 10):
q.put(i)
if __name__ == '__main__':
# 定义队列
queue = Queue()
# 定义进程p1,目标函数为Process1
p1 = Process(target=Process1, args=(queue, ))
# 启动p1进程
p1.start()
# 定义进程p2,目标函数为Process2
p2 = Process(target=Process2, args=(queue, ))
# 启动p2进程
p2.start()
# 定义进程p3,目标函数为Process3
p3 = Process(target=Process3,args=(queue,))
# 启动p2进程
p3.start()
time.sleep(1)
# 强制停止p1、p2进程
p1.terminate()
p2.terminate()
结果: Process1 recv message is 0
Process2 recv message is 1
Process1 recv message is 2
Process2 recv message is 3
Process1 recv message is 4
Process2 recv message is 5
Process1 recv message is 6
Process2 recv message is 7
Process1 recv message is 8
Process2 recv message is 9
四、总结
线程、进程,这是我们编程中一个重要的概念,毕竟我们不可能一个进程,一个顺序下去执行完我们的代码,所以掌握了这两项,你就可以任意掌控你的代码,让他产生很多让你惊讶的效果,所以这两个大家一定要在代码中熟用