并发编程学习笔记
今日内容概要
- 作业讲解
- 互斥锁
- 线程理论
- 创建线程的两种方式
- 多线程实现TCP服务端并发
- join方法
- 同一个进程下线程间数据共享
- 线程对象相关方法
- 守护线程
- GIL全局解释器锁
今日内容详细
作业讲解
多进程实现TCP服务端的并发效果
import socket
from multiprocessing import Process
# 方式2
def get_server():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
return server
def talk(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
if __name__ == '__main__':
# 方式1
# server = socket.socket()
# server.bind(('127.0.0.1', 8080))
# server.listen(5)
server = get_server()
while True:
sock, addr = server.accept()
# 开设进程去完成数据交互
p = Process(target=talk, args=(sock,))
p.start()
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'))
互斥锁
互斥锁:将并发变成串行,虽然牺牲了程序的执行效率但是保证了数据安全
from multiprocessing import Process, Lock
mutex = Lock()
mutex.acquire() # 抢锁
mutex.release() # 释放锁
"""
多个程序同时操作一份数据的时候很容易产生数据错乱!!!
为了避免数据错乱 我们需要使用互斥锁
"""
强调:
互斥锁只应该出现在多个程序操作数据的地方,其他位置尽量不要加
ps:以后我们自己处理锁的情况很少,只需要知道锁的功能即可
锁的类别
类别 | 作用 |
---|---|
行锁 | 将一行的数据只能让一人读取 |
表锁 | 将整个表的数据只让一个人读取 |
乐观锁 | 都能查看数据,只要有一个人修改数据就放弃操作 |
悲观锁 | 只要有一个人在操作数据时就直接上锁,直到操作完才释放 |
线程
线程理论
分类 | ||
---|---|---|
进程 | 资源单位 | 相当于是车间,进程负责给内部的线程提供相应的资源 |
线程 | 执行单位 | 相当于是车间里面的流水线,线程负责执行真正的功能 |
1.一个进程至少含有一个线程
2.同一个进程下多个线程之间资源共享
分类 | 区别 |
---|---|
多进程 | 需要申请内存空间,需要拷贝全部代码 资源消耗大 |
多线程 | 不需要申请内存空间,也不需要拷贝全部代码,资源消耗小 |
创建线程的两种方式
进程与线程的代码实操几乎是一样的。
from threading import Thread
import time
# 第一种
def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is over')
"""
开设线程不需要完整拷贝代码 所以无论什么系统都不会出现反复操作的情况
也不需要在启动脚本中执行 但是为了兼容性和统一性 习惯在启动脚本中编写
"""
# 创建线程无需在__main__下面编写 但是为了统一 还是习惯在子代码中写
t = Thread(target=task, args=('jason', ))
t.start() # 创建线程的开销极小 几乎是一瞬间就可以创建
print('主线程')
if __name__ == '__main__':
t = Thread(target=task, args=('jason',))
t.start()
print('主线程')
# 第二种
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('jason')
obj.start()
print('主线程')
多线程实现TCP服务端并发
比多进程更加简单方便 消耗的资源更少
# 服务端
import socket
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen()
def talk(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
while True:
sock, addr = server.accept()
# 每类一个客户端就创建一个线程做数据交互
t = Thread(target=talk, args=(sock,))
t.start()
# 客户端
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)
线程对象相关方法
1.进程号
同一个进程下开设的多个线程拥有相同的进程号
2.线程名
from threading import Thread, current_thread
current_thread().name
主:MainThread 子:Thread-N
3.进程下的线程数
active_count()
例:
from threading import Thread, active_count, current_thread
import os
import time
class MyThread(Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(self.name)
t1 = MyThread('jason')
t2 = MyThread('kevin')
t1.start()
t2.start()
def task():
time.sleep(1)
# print('子线程获取进程号>>>:',os.getpid())
print(current_thread().name)
t = Thread(target=task)
t1 = Thread(target=task)
t.start()
t1.start()
print(active_count())
print(current_thread().name)
print('主线程获取进程号>>>:', os.getpid())
守护线程
守护线程伴随着被守护的线程的结束而结束
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
print('主线程')
"""
进程下所有的非守护线程结束 主线程(主进程)才能真正结束!!!
"""
GIL全局解释器锁
官方文档对GIL的解释
储备知识
python解释器也是由编程语言写出来的
Cpython 用C写出来的
Jpython 用Java写出来的
Pypython 用python写出来的
ps:最常用的就是Cpython(默认)
"""纯理论 不影响编程 只不过面试的时候可能会被问到"""
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
# 翻译
"""
1.GIL的研究是Cpython解释器的特点 不是python语言的特点
2.GIL本质也是一把互斥锁
3.GIL的存在使得同一个进程下的多个线程无法同时执行(关键)
言外之意:单进程下的多线程无法利用多核优势 效率低!!!
4.GIL的存在主要是因为cpython解释器中垃圾回收机制不是线程安全的
"""
理解
GIL的存在是必要的,如果不存在会产生垃圾回收机制与正常线程之间数据错乱,GIL是加在CPython解释器上面的互斥锁。同一个进程下的多个线程要想执行必须先抢GIL锁,所以同一个进程下多个线程肯定不能同时运行,也就是无法利用多核优势。
结论
参考群内截图 理解上述3、4解读
1.误解:python的多线程就是垃圾 利用不到多核优势
python的多线程确实无法使用多核优势 但是在IO密集型的任务下是有用的
2.误解:既然有GIL 那么以后我们写代码都不需要加互斥锁
不对 GIL只确保解释器层面数据不会错乱(垃圾回收机制)
针对程序中自己的数据应该自己加锁处理
3.所有的解释型编程语言都没办法做到同一个进程下多个线程同时执行
ps:我们平时在写代码的时候 不需要考虑GIL 只在学习和面试阶段才考虑!!!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了