线程理论
1 什么是线程
进程其实一个资源单位(开启一个内存空间,里面放应该执行的代码,代码运行产生的数据),
而进程内的线程才是cpu上的执行单位
进程是资源单位 qq资源 相当于一个车间 造发动机和造轮胎能共享吗
线程其实指的就是代码的执行过程(开空间没关系,数据往进程去要)
线程就是车间流水线 跟车间要
至少有一个线程 一个车间也可以有多少流水线
2 为何要用线程
线程vs进程
1. 同一进程下的多个线程共享该进程内的资源
2. 创建线程的开销要远远小于进程
并发2种
多进程实际上是每个进程里面单独一个线程
由于进程当中资源不共享
并发多个任务需要通信需要利用管道或者队列
多线程是指同一个进程里面多个线程
本身同一个进程里面多个线程资源就共享
所以不需要借助任何的机制,数据之间就可以交互
3 如何用线程
from threading import Thread
def task(name):
print('%s is running'%name)
time.sleep(2)
print('%s is done'%name)
if __name__ == '__main__':
t = Thread(target=task,args=('线程1',))
# 造线程非常快,因为不用开辟空间了
t.start()
print('主')
守护线程
主线程掌管了这个进程里面的资源
主线程不会死了,资源是来自主线程的
主线程是这个进程里面的老大
等待所有的子线程死了才死
车间里面的厂长,员工下班了他才下班
from threading import Thread
import time
def foo():
print(123)
time.sleep(1)
print('end123')
def bar():
print(456)
time.sleep(3)
print('end456')
if __name__ == '__main__':
# 线程
t1 = Thread(target=foo)
t2 = Thread(target=bar)
#开启t1守护线程
t1.daemon = True
# # 造线程非常快,因为不用开辟空间了
t1.start()
t2.start()
print('主')
因为456线程没有死掉
守护线程 》》主线程 》》非守护线程
按时下班的员工》》老板 》》加班的员工
多进程和多线程的应用场景
计算密集型:应该使用多进程,运行速度更快
IO密集型: 应该开启多线程,运行速度更快
线程自定义互斥锁
from threading import Thread,Lock
import time
mutex = Lock()
n = 100
def task():
global n
# 线程1加锁
mutex.acquire()
temp = n
# 在这个时间消耗完之前,后面的99个线程都进来了
# 并且拿到的是temp=100
# 效率高了,不安全
# IO操作切换线程
time.sleep(0.1)
n = temp - 1
# 线程1计算完成释放锁
mutex.release()
if __name__ == '__main__':
t_l = []
start = time.time()
for i in range(100):
t = Thread(target=task)
t_l.append(t)
t.start()
for t in t_l:
t.join()
print(n,time.time()-start)
GIL锁
一、什么是GIL(全局解释器锁)
互斥锁就是把多个任务的共享数据的修改由并发变成串行
代码运行先拿到cpu的权限,还需要把代码丢给解释器,再在进程里面的线程运行
GIL本质就是一把互斥锁,相当于执行权限
每个进程内都会存在一把GIL,同一进程内的多个线程
必须抢到GIL之后才能使用解释器来执行自己的代码,
即同一进程下的多个线程无法实现并行,
用不了多核(多个cpu)优势
但是可以实现并发
因为多线程是遇到io操作就会释放GIL锁
二、为何要有GIL
垃圾回收机制不是线程安全的
每个进程内都会存在一把GIL
意味着有锁才能计算
多进程适合处理计算密集型
多线程适合处理io密集型 所以多线程多核优势没有意义
三、如何用GIL
有了GIL,应该如何处理并发
from threading import Thread
import time
def task(name):
print('%s is running'%name)
time.sleep(2)
if __name__ == '__main__':
t1 = Thread(target=task,args=('线程1',))
t2 = Thread(target=task,args=('线程2',))
t3 = Thread(target=task,args=('线程3',))
# 造线程非常快,因为不用开辟空间了
t1.start()
t2.start()
t3.start()
基于tcp协议的套接字(链接循环+通讯循环版本+并发)
tcpsockserver服务器
class MyTCPhanler(socketserver.BaseRequestHandler):
def handle(self):
while True:
try:
data = self.request.recv(1024)
print('收到客户端数据', data)
# 变大写发送回去
self.request.send(data.upper())
except ConnectionResetError:
break
if __name__ == '__main__':
# 通信循环
server = socketserver.ThreadingTCPServer(('127.0.0.1',8081),MyTCPhanler)
# 链接循环
server.serve_forever()
服务端
客户端关闭了服务端会正常结束
服务端必须满足至少三点:
一、绑定一个固定的ip和port
二、一直对外提供服务,稳定运行
三、能够支持并发,因为是io密集型要开多线程
import socket
from threading import Thread
def communicate(conn):
# 通信循环
while True:
try:
data = conn.recv(1024)
print('收到客户端数据', data)
# 变大写发送回去
conn.send(data.upper())
except ConnectionResetError:
break
def server(ip,port,backlog=5):
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind((ip,port))
server.listen(backlog)
while True:
# 链接循环
conn,client_addr = server.accept()
# 通信循环
t = Thread(target=communicate,args=(conn,))
t.start()
if __name__ == '__main__':
s = Thread(target=server,args=('127.0.0.1',8081))
s.start()
客户端
import socket
# 1买手机
# AF_INET 互联网协议
# SOCK_STREAM TCP流式协议,
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# print(phone)
# 2.拨号
# # 建立三次握手
phone.connect(('127.0.0.1',8081))
# 3.发/收消息
# 必须传入二进制,
# 物理层
while True:
msg = input('>>>').strip()
if len(msg)==0:continue
phone.send(msg.encode('utf-8'))
# 收
data = phone.recv(1024)
print('收到服务端数据',data)
# 4关机
phone.close()