一、 线程理论
进程是资源单位
进程相当于工厂的车间 进程负责给内部的线程提供相应的资源
线程是执行单位
线程相当于车间里面的流水线 线程负责执行真正的功能
多个线程就多个流水线都是在进程的资源空间内
线程的特征:
1.一个进程至少有一个线程(没有进程哪来的线程 程序(客户端)没有运行什么都没有)
2.多进程与多线程的区别
多进程 需要申请内存空间 需要拷贝全部代码 资源消耗大 数据间不能共享
多线程 不需要申请内存空间 也不需要拷贝全部代码 资源消耗小 数据间可以共享
二、 创建线程的两种方式
开设线程不需要完整拷贝代码 所以无论什么系统都不会出现反复操作的情况
也不需要在启动脚本中执行 但是为了兼容性和统一性 习惯在启动脚本中编写
第一种方式
from threading import Thread
import time # 导入线程模块
def task(name): # 设置函数
print(f'{name}正在运行')
time.sleep(3) # 休眠三秒
print(f'{name}运行结束')
if __name__ == '__main__': # 设置运行脚本 兼容性提升
t = Thread(target=task, args=('LebronJames',))
t.start()
print('主线程')
第二种方式
rom threading import Thread
import time
class MyThread(Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(f'{self.name}正在运行')
time.sleep(3)
print(f'{self.name}运行结束')
obj = MyThread('LebronJames')
obj.start()
print('主线程')
'''两个输出结果: LebronJames正在运行 主线程 LebronJames运行结束!!!'''
三、 多线程实现TCP服务端并发
比多进程更加简单方便 消耗的资源更少
Server:
import socket
from threading import Thread # 导入模块
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5) # 设置端口
def talk(sock): # 设置一个函数 接收数据 发送数据
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
while True:
sock, addr = server.accept()
p = Thread(target=talk, args=(sock,)) # 开设进程去完成数据交互
p.start()
Clinet:
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080)) # 设置固定的地址
while True:
client.send(b'hello baby') # 发送数据
data = client.recv(1024)
print(data.decode('utf8'))
四、 Join方法
主线程等到子线程运行结束之后再运行(跟主线程一个道理)
from threading import Thread
import time
def task():
print('正在执行')
time.sleep(3)
print('运行结束')
t = Thread(target=task)
t.start()
t.join()
print('主线程') # 输出结果 : 正在执行 运行结束 主线程
五、 同一个进程下线程数据共享
from threading import Thread
money = 1000
def func():
global money # 同一个进程下 线程之间数据共享
money = 666
t = Thread(target=func)
t.start()
t.join() # 确保线程运行完毕 再查找money 结果更具有说服性
print(money) # 输出结果: 666
六、 线程对象相关方法
1. 进程号
同一个进程下开设的多个线程拥有相同的进程号
2.线程名
需要使用到模块 from threading import Thread, current_thread
current_thread().name
主:MainThread 子:Thread-N
3. 进程下的线程数 模块 active_count
active_count()
from threading import Thread, current_thread, active_count
import os
money = 1000
def func():
global money
money = 666
print('子线程名', current_thread())
t = Thread(target=func)
t.start()
t.join() # 确保线程运行完毕 再查找money 结果更具有说服性
print('子进程', os.getpid())
print('进程下的线程数',active_count())
输出结果:
子线程名 <Thread(Thread-1, started 13071642624)>
子进程 3233
进程下的线程数 1
七、 守护线程
守护线程伴随着被守护的线程的结束而结束
进程下所有的守护线程 主线程(主进程)结束 所有线程结束全部直接结束
进程下所有的非守护线程结束 主线程(主进程)才能真正结束!!!(没有被守护的结束了主线程才结束)
from threading import Thread
import time
def task():
print('子线程运行task函数')
time.sleep(3)
print('子线程运行task结束')
t = Thread(target=task)
t.daemon = True # 伴随主线程 主线程结束子进程也结束
t.start()
# t.daemon = True # 不能放在start下面 否则报错!
print('主线程')
输出结果 : 子线程运行task函数 主线程
八、 GIL全局解释器锁
储备知识:
1.python解释器也是由编程语言写出来的
Cpython 用C写出来的 Jpython 用Java写出来的 Pypython 用python写出来的
2.GIL的研究是Cpython解释器的特点 不是python语言的特点
3.GIL本质也是一把互斥锁
4.GIL的存在使得同一个进程下的多个线程无法同时执行(关键)
言外之意:单进程下的多线程无法利用多核优势 效率低!!!
误解:
1.python的多线程就是垃圾 利用不到多核优势
python的多线程确实无法使用多核优势 但是在IO密集型的任务下是有用的
2.既然有GIL 那么以后我们写代码都不需要加互斥锁
不对 GIL只确保解释器层面数据不会错乱(垃圾回收机制)
针对程序中自己的数据应该自己加锁处理
技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助
请点点赞收藏+关注
谢谢支持!