今日内容概要
- 开启线程的两种方式
- TCP服务端实现并发效果
- 线程对象的join方法
- 线程间数据共享
- 线程对象属性及其他方法
- 守护线程
- 线程互斥锁
- GIL全局解释器锁
- 多进程与多线程的实际应用场景
今日内容详细
开启线程的两种方式
# import time
# from multiprocessing import Process
# from threading import Thread
#
#
# def task(name):
# print('%s is running' % name)
# time.sleep(1)
# print('%s is over' % name)
#
#
# # 开启线程不需要在main下面执行代码 直接书写可以
# # 但是我们还是习惯性的将启动命令写在下面
# t = Thread(target=task, args=('egon',))
# t.start() # 创建线程的开销非常小,几乎是代码一执行线程就已经创建了
# print('主')
from threading import Thread
import time
class MyThread(Thread):
def __init__(self,name):
# 重写了别人的方法 又不知道别人的方法里有啥,你就调用父类的方法
super().__init__()
self.name = name
def run(self):
print("%s is running" % self.name)
time.sleep(1)
print('egon dsb')
if __name__ == '__main__':
t = MyThread('egon')
t.start()
print('主')
TCP服务端实现平发的效果
import socket
from threading import Thread
from multiprocessing import Process
"""
服务端
1.要有固定的IP和PORT
2.24小时不间断提供服务
3.能够支持并发
"""
server = socket.socket() # 括号内不加参数默认就是TCP
server.bind(('127.0.0.1', 8080))
server.listen(5)
# 将服务的代码单独封装成一个函数
def task(conn):
# 通信循环
while True:
try:
data = conn.recv(1024)
# 针对mac linux 客户端链接后
if len(data) == 0: break
print(data.decode('utf-8'))
conn.send(data.upper())
except ConnectionError as e:
print(e)
break
conn.close()
# 链接循环
while True:
conn, addr = server.accept() # 接客
# 叫其他人来服务客户
t = Thread(target=task,args=(conn,))
# t = Process(target=task,args=(conn,))
t.start()
"""客服端"""
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
client.send('hello word'.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
线程对象的join方法
from threading import Thread
import time
def task(name):
print('%s is running' % name)
time.sleep(3)
print('%s over' % name)
if __name__ == '__main__':
t = Thread(target=task,args=('egon', ))
t.start()
t.join() # 主线程等待子线程结束
print('主')
同一个进程下的多个线程数据是共享的
from threading import Thread
import time
money = 100
def task():
global money
money = 666
print(money)
if __name__ == '__main__':
t = Thread(target=task)
t.start()
t.join()
print(money) # 666
线程对象属性及其他方法
from threading import Thread, active_count, current_thread
import os, time
def task(n):
# print('hello world', os.getpid())
print('hello world', current_thread().name)
time.sleep(n)
if __name__ == '__main__':
t = Thread(target=task, args=(1,))
t1 = Thread(target=task, args=(2,))
t.start()
t1.start()
t1.join()
print('主',active_count()) # 统计当前正在活跃的线程数
# print('主',current_thread().name)
# print('主',os.getpid())
守护线程
# from threading import Thread
# import time
#
#
# def task(name):
# print('%s is running' % name)
# time.sleep(1)
# print('%s over' % name)
#
#
# if __name__ == '__main__':
# t = Thread(target=task, args=('egon',))
# t.daemon = True
# t.start()
# print('主')
"""
主线程结束后不会立刻结束 需要等待其他子线程结束才会结束
因为主线程结束意味着所在的进程的结束
"""
from threading import Thread
import time
def foo():
print(123)
time.sleep(1)
print('end123')
def func():
print(456)
time.sleep(3)
print('end456')
if __name__ == '__main__':
t1 = Thread(target=foo)
t2 = Thread(target=func)
t1.daemon = True
t1.start()
t2.start()
print('主')
线程的互斥锁
from threading import Thread, Lock
import time
money = 100
mutex = Lock()
def task():
global money
mutex.acquire()
tmp = money
time.sleep(0.1)
money = tmp - 1
mutex.release()
if __name__ == '__main__':
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(money)
GIL全局解释器锁
"""
cpython
Jpython
pypypython
但是普遍使用的都是cpython
因为cpython中的内存管理不是线程安全的
内存管理(垃圾回收机制)
1.应用计数
2.标记清楚
3.分代回收
"""
"""
重点:
1.GIL不是python的特点而是cpython解释器的特点
2.GIL是保证解释器级别的数据安全
3.GIL会导致同一个进程下的多个线程的无法同时执行
4.针对不同的数据还是需要加不同的锁处理
5.解释性语言的通病:同一个进程下多线程无法执行
"""
GIL与普通互斥锁的区别
from threading import Thread, Lock
import time
money = 100
mutex = Lock()
def task():
global money
mutex.acquire()
tmp = money
time.sleep(0.1)
money = tmp - 1
mutex.release()
if __name__ == '__main__':
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(money)
"""
100个线程起起来之后, 要先去抢GIL
我进入IO GIL自动释放 但我手上还有一个自己的互斥锁
其他线程虽然抢到了但是抢不到互斥锁
最终GIL还是回到自己的手上 自己去操作数据
"""
同一个进程下的多线程无法利用多核优势,是不是就没有了
"""
多线程是否有用要看具体情况
单核:四个任务(IO密集型\计算密集型)
多核:四个任务(IO密集型\计算密集型)
"""
# 计算密集型 每个任务都需要10s
单核(不用考虑)
多进程:额外消耗资源
多线程:节省开销
多核
多进程:总耗时 10+
多线程:总耗时 40+
# IO密集型 每个任务都需要10s
多核
多进程:相对浪费资源
多线程:更加节省资源
代码验证
# 计算机密集型
# from threading import Thread
# from multiprocessing import Process
# import time
# import os
#
#
# def work():
# res = 1
# for i in range(1, 100000):
# res *= i
#
#
# if __name__ == '__main__':
# l = []
# print(os.cpu_count())
# start_time = time.time()
# for i in range(4):
# # p = Process(target=work) # 4.017219066619873
# # p.start()
# # l.append(p)
# t = Thread(target=work) # 13.573662042617798
# t.start()
# l.append(t)
# for p in l:
# p.join()
# print(time.time() - start_time)
# IO密集型
from threading import Thread
from multiprocessing import Process
import time
import os
def work():
time.sleep(2)
if __name__ == '__main__':
l = []
print(os.cpu_count())
start_time = time.time()
for i in range(200):
# p = Process(target=work) # 16.503886699676514
# p.start()
# l.append(p)
t = Thread(target=work) # 2.04239821434021
t.start()
l.append(t)
for p in l:
p.join()
print(time.time() - start_time)
总结
"""
多进程和多线程都有各自的优势,并且后面写项目的时候通常都是多进程下面再开多线程
这样既可以利用多核要可以节省资源消耗
"""
内容回顾
-
开启线程的两种方式
""" 开进程和开启线程的步骤基本都是一样的 只是导入的模块不一样而已 开进程必须写在main下面而开线程无需这么做 类的对象调用方法 类的继承重写run方法 """
-
TCP服务端实现并发
"""将接客与服务的活分开"""
-
线程对象join方法
"""等待当前线程对象结束之后 再继续往下执行"""
-
同一个进程内的多个数据是共享的
""" 同一个进程可以开启多个线程 进程:资源单位 线程:执行单位 """
-
线程对象属性和方法
""" current_thread.name active_count 统计当前正在活跃的线程 """
-
守护线程
""" 主线程必须等待所有非守护线程的结束后才能结束 t.daemon = True t.start() """
-
线程互斥锁
""" 当多个线程在操作同一份数据的时候会造成数据错乱 这个时候为了保证数据安全 通常加锁处理 锁: 将并发变成串行,降低了程序的运行效率但保证了数据安全 """
-
GIL全局解释器锁
""" 1.GIL是cpython解释器的特点不是python的特点!!! 2.GIL本质也是一把互斥锁 解释器级别的锁 3.它的存在是因为cpython解释器内存管理不是线程安全的 垃圾回收机制 引用技术 标记清除 分代回收 4.也就意味着GIL的存在导致了一个进程下的多个线程无法利用多核优势 5.针对不同的数据应该加不同的锁来处理 """