并发编程(三)
线程
简介
线程是一个资源单位,真正被CPU执行的其实是进程里面的线程,例如打开word是打开一个进程,那么在里执行写,读,插入图片等操作就是进行一个个线程操作;
通俗理解:进程类似于工厂,线程类似于是工厂里面的一条条流水线,所有的进程肯定是最少有一个线程;
进程间数据默认是隔离的,但是同一个进程内的多个线程数据是共享的;
开设线程的两种方式
- 开设进程需要做哪些操作
重新申请一块内存空间 将所需的资源全部导入
- 开设线程需要做哪些操作
上述两个步骤都不需要 所以开设线程消耗的资源远比开设进程的少
方法一:
from threading import Thread
import time
def test(name):
print(f'{name}is running!')
time.sleep(2)
print(f'{name}is over!')
# 开设线程可以不用在main判断语句下开设
t = Thread(target=test,args=('Hammer',))
t.start()
print('主进程')
# 线程不需要考虑内存空间和资源的问题,也不需要等待主进程执行完再执行子进程等问题
# 所以开设线程所需要的时间更短,速度更快
方法二:
# 和进程类似,继承的方式
class Myclass(Thread):
def __init__(self,name):
super().__init__()
self.name = name
def run(self):
print(f'{self.name} is running !')
time.sleep(2)
print(f'{self.name} is over !')
t = Myclass('Hammer')
t.start()
线程对象的join方法
在进程中的join方法是,主进程等待子进程执行完再运行;
线程中顾名思义是一样的,主线程等待子线程执行完再执行
from threading import Thread
import time
def test(name):
print(f'{name} is running!')
time.sleep(2)
print(f'{name} is over! ')
# 开设线程
t = Thread(target=test,args=('子线程',))
t.start()
t.join()
print('主线程')
通过os.getpid()方法获取进程号来验证两个线程同属于一个进程
from threading import Thread
import time
import os
def test(name):
print(os.getpid()) # 9436
print(f'{name} is running!')
time.sleep(2)
print(f'{name} is over! ')
# 开设线程
t = Thread(target=test,args=('子线程',))
t.start() # 9436
print(os.getpid())
print('主线程')
线程之active_count模块
统计当前活跃的线程数
from threading import Thread,active_count
import time
def test(name):
print(f'{name} is running !')
time.sleep(2)
print(f'{name} is over !')
t = Thread(target=test,args=('线程1',))
t1 = Thread(target=test,args=('线程2',))
t.start()
t1.start()
print(active_count()) # 3
# 现有活跃线程数为3的原因是主线程加2个子线程
线程之current_thread模块
获取当前线程的名字
from threading import Thread,current_thread
import time
def test(name):
print(f'子线程名:>>>{current_thread().name}')
print(f'{name} is running !')
time.sleep(2)
print(f'{name} is over!')
t = Thread(target=test,args=('线程1',))
t1 = Thread(target=test,args=('线程2',))
t.start()
t1.start()
print(f'主线程名:>>> {current_thread().name}')
# 结果
子线程名:>>>Thread-1
线程1 is running !
子线程名:>>>Thread-2
线程2 is running !
主线程名:>>> MainThread
线程2 is over!
线程1 is over!
# 子线程可以通过t.name直接拿,主线程必须通过方法.name拿
守护线程
类似守护进程一样,一个线程结束其他线程也得结束(陪葬)
主线程的结束意味着整个进程的结束,所以主线程需要等待里面所有非守护线程的结束才能结束
from threading import Thread
import time
def test(name):
print(f'{name} is running ')
time.sleep(2)
print(f'{name} is over !')
t = Thread(target=test,args=('子线程',))
t.daemon = True # 守护线程必须放在start上
t.start()
print('主线程')
迷惑的例子,判断打印顺序
from threading import Thread
from multiprocessing import Process
import time
def foo():
print(123)
time.sleep(3)
print("end123")
def bar():
print(456)
time.sleep(1)
print("end456")
if __name__ == '__main__':
t1=Thread(target=foo)
t2=Thread(target=bar)
t1.daemon=True
t1.start()
t2.start()
print("main-------")
# 结果
123
456
main-------
end123
end456
# 结果分析
开设线程直接打印123,然后遇到sleep方法
打印456,遇到sleep方法
打印main
这时候主线程没有直接结束,等待非守护线程(bar)结束才能结束,然而bar()方法中,sleep睡了3秒,那么print('end123')睡1秒就打印了
线程数据共享
验证同一个进程内线程间数据共享
注意是同一进程下!
from threading import Thread
money = 100
def test():
global money
money = 999
t = Thread(target=test)
t.start()
t.join()
print(money)
# 结果
999
# 这样是修改了的,和进程不一样
线程互斥锁
多个线程修改数据和多进程修改数据一样容易出现数据错乱,这样需要线程互斥锁来解决这个问题;
对比进程互斥锁中的查票买票代码理解;
from threading import Thread, Lock
from multiprocessing import Lock
import time
num = 100
def test(mutex):
global num
mutex.acquire() # 抢锁
# 先获取num的数值
tmp = num
# 模拟延迟效果
time.sleep(0.1)
# 修改数值
tmp -= 1
num = tmp
mutex.release() # 释放锁
t_list = []
# 声明锁
mutex = Lock()
for i in range(100):
t = Thread(target=test, args=(mutex,))
t.start()
t_list.append(t)
# 确保所有的子线程全部结束
for t in t_list:
t.join()
print(num)
# 如果不加锁,那么输出的num是99,比如减100次应该是0,在这里并发操作不加锁会产生数据的错乱,需要变成串行
补:TCP服务端实现并发
服务端
import socket
from threading import Thread
from multiprocessing import Process
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
# 和用户交互的代码封装
def talk(sock):
while True:
try:
data = sock.recv(1024)
if len(data) == 0: break
print(data.decode('utf8'))
sock.send(data + b'Hi!')
except ConnectionResetError as e:
print(e)
break
sock.close()
while True:
sock, addr = server.accept()
print(addr)
# 开设多进程或者多线程
t = Thread(target=talk, args=(sock,))
t.start()
客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
whlie True:
client.send(b'hello')
data = client.recv(1024)
print(data.decode('utf8'))