Python并发编程

Eecho
Eecho
发布于 2024-12-25 / 228 阅读
24
0

Python并发编程


进程

创建进程的方式

# -*- coding: utf-8 -*-
# @Time : 2023/8/14 18:22
# @Author : 4C69
​
# 方式1
from multiprocessing import Process
import time
​
​
def func(name):
    print(f'{name}任务开始')
    time.sleep(2)
    print(f'{name}任务执行完毕')
​
​
if __name__ == '__main__':
    # 1、得到进程操作对象
    p = Process(target=func, args=('li',))   # 注意args传入的是元组
    # 2、创建进程
    p.start()
    print('主进程')
    
    
# 运行结果
主进程
li任务开始
li任务执行完毕
# -*- coding: utf-8 -*-
# @Time : 2023/8/14 18:22
# @Author : 4C69
​
# 方式2
from multiprocessing import Process
import time
​
​
class MyProcess(Process):
    def __init__(self, name):
        super().__init__()
        self.name = name
​
    def run(self) -> None:
        print(f'{self.name}开始')
        time.sleep(2)
        print(f'结束')
​
​
if __name__ == '__main__':
    p = MyProcess('li')
    p.start()
    print('主进程')
    
# 运行结果
主进程
li开始
结束

Windows如果不加 if name == 'main':报错原因

在windows上创建进程,会使用类似模块导入的方式在子进程里面导入模块,也就是导入当前python文件,而导入模块会把模块里的代码都执行一遍,从而进入了死循环。

Linux,Unix中创建进程会把对应的代码以及当前进程的数据集(变量)全部都拷贝一份,然后在子进程中执行任务,所以Linux,Unix不会报错。

总结:创建进程就是在内存中申请一块内存空间,然后把需要运行的代码放进去,多个进程的内存空间它们彼此是隔离的,进程

与进程之间的数据,它们是没有办法直接交互的,如果需要交互,则可以借助第三方工具(模块)

join方法

join()等待子进程执行完毕后再执行主进程

# -*- coding: utf-8 -*-
# @Time : 2023/8/14 18:22
# @Author : 4C69
​
from multiprocessing import Process
import time
​
​
def func(name, n):
    print(f'{name}任务开始')
    time.sleep(n)
    print(f'{name}任务执行完毕')
​
​
if __name__ == '__main__':
    start = time.time()
    # p1 = Process(target=func, args=('写讲话稿', 1))
    # p2 = Process(target=func, args=('写讲话稿', 2))
    # p3 = Process(target=func, args=('写讲话稿', 3))
    # p4 = Process(target=func, args=('写讲话稿', 4))
    #
    # p1.start()
    # p2.start()
    # p3.start()
    # p4.start()
    #
    # p1.join()
    # p2.join()
    # p3.join()
    # p4.join()
​
    # 优化
    l  =[]
    for i in range(1, 5):
        p = Process(target=func, args=(f'写讲话稿{i}', i))
        p.start()
        l.append(p)
    for p in l:
        p.join()
    end = time.time()
    print(end-start)
    
    
# 运行结果
写讲话稿1任务开始
写讲话稿2任务开始
写讲话稿3任务开始
写讲话稿4任务开始
写讲话稿1任务执行完毕
写讲话稿2任务执行完毕
写讲话稿3任务执行完毕
写讲话稿4任务执行完毕
4.099806547164917

进程之间的数据隔离

# -*- coding: utf-8 -*-
# @Time : 2023/8/8 18:08
# @Author : 4C69
​
from multiprocessing import Process
​
age = 18
def func():
    global age
    age = 16
​
​
if __name__ == '__main__':
    p = Process(target=func)
    p.start()
    p.join()
    print(age)
    
# 运行结果
18

不同进程之间的数据是隔离的,子进程起来的时候会复制主进程里面的变量

进程号

pid号(进程号)

PID全称为:Process Identifier

系统每打开一个程序,就会分配一个进程编码

进程ID是暂时,也是唯一的

比如:Chrome.exe占用了17652的PID,该进程在没有关闭之前,则会一直使用17652的PID,其 它的进程在此之前都无法使用该PID,这就是它的唯一性。

当Chrome.exe关闭后再开启,系统则会重新匹配PID,可能不是原来的那个17652的PID,这就是它的暂时性。

# -*- coding: utf-8 -*-
# @Time : 2023/8/8 18:08
# @Author : 4C69
​
from multiprocessing import Process,current_process
import time
import os
​
def task(name='子进程'):
    print(f'任务{current_process().pid}执行中')
    print(f'{name}{os.getpid()}执行中')
    print(f'{name}的父进程{os.getppid()}执行中')
​
​
if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    # p.terminate()  杀死当前进程(win: taskkill pid, mac/linux: kill -9 pid)
    time.sleep(1)
    # print(p.is_alive())    判断当前进程是否存活,返回bool值
    print('主进程')
    task('主进程')
    
    
#运行结果
任务23656执行中
子进程23656执行中
子进程的父进程34104执行中
主进程
任务34104执行中
主进程34104执行中
主进程的父进程21648执行中   #主进程的父进程,因为当前的python文件也要运行

僵尸进程和孤儿进程

  • 僵尸进程

"""
⼦进程死后,还会有⼀些资源占⽤(进程号,进程运⾏状态,运⾏
时间等),等待⽗进程通过系统调⽤回收(收⼫)
除了init进程之外,所有的进程,最后都会步⼊僵⼫进程
危害:
⼦进程退出之后,⽗进程没有及时处理,僵⼫进程就会⼀直占
⽤计算机资源
如果产⽣了⼤量的僵⼫进程,资源过度占⽤,系统没有可⽤的
进程号,导致系统不能产⽣新的进程
"""
  • 孤儿进程

"""
⼦进程处于存活状态,但是⽗进程意外死亡
操作系统会开设⼀个“孤⼉院”(init进程),⽤来管理孤⼉进
程,回收孤⼉进程的相关资源
"""

守护进程

一个进程守护另一个进程

守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机才随之一起停止运行。

# -*- coding: utf-8 -*-
# @Time : 2023/8/8 18:08
# @Author : 4C69
​
from multiprocessing import Process
import time
​
​
def task(name):
    print(f'{name}还活着')
    time.sleep(5)
    print(f'{name}正常死亡')
​
​
if __name__ == '__main__':
    p = Process(target=task, args=('苏妲己',))
    p.start()
    print(f'纣王驾崩了')
    
    
# 运行结果
纣王驾崩了
苏妲己还活着
苏妲己正常死亡
# -*- coding: utf-8 -*-
# @Time : 2023/8/8 18:08
# @Author : 4C69

from multiprocessing import Process
import time


def task(name):
    print(f'{name}还活着')
    time.sleep(5)
    print(f'{name}正常死亡')


if __name__ == '__main__':
    p = Process(target=task, args=('苏妲己',))
    p.daemon = True
    p.start()
    time.sleep(2)
    print(f'纣王驾崩了')
    
    
# 运行结果
苏妲己还活着
纣王驾崩了

互斥锁

当多个进程操作同一份数据的时候,会出现数据错乱的问题,解决方法就是加锁处理。

把并发变成串行,牺牲了效率,但保证了数据的安全

注意:加锁只应该在争抢数据的环节加

# tickets.json
{"tickets_num": 2}
# -*- coding: utf-8 -*-
# @Time : 2023/8/20 21:04
# @Author : 4C69


from multiprocessing import Process,Lock
import random
import time
import json


# 查票
def search_ticket(name):
    with open(r'data/tickets.json', mode='rt', encoding='utf-8')as f:
        dic = json.load(f)
        print(f'用户{name}查询余票:{dic.get("tickets_num")}')


# 买票
def buy_ticket(name):
    with open(r'data/tickets.json', mode='rt', encoding='utf-8') as f:
        dic = json.load(f)
    time.sleep(random.randint(1,5))
    if dic['tickets_num'] > 0:
        dic['tickets_num'] -= 1
        with open(r'data/tickets.json', mode='wt', encoding='utf-8')as f:
            json.dump(dic, f)
        print(f'用户{name}购买成功')
    else:
        print(f'余额不足,用户{name}买票失败')


def task(name):
    search_ticket(name)
    buy_ticket(name)


if __name__ == '__main__':
    mutex = Lock()
    for i in range(1,11):
        p = Process(target=task, args=(f'{i}',  ))
        p.start()

        
# 结果
用户1查询余票:2
用户2查询余票:2
用户3查询余票:2
用户4查询余票:2
用户5查询余票:2
用户6查询余票:2
用户7查询余票:2
用户8查询余票:2
用户9查询余票:2
用户10查询余票:2
用户2购买成功
用户4购买成功
用户7购买成功
用户10购买成功
用户1购买成功
用户3购买成功
用户6购买成功
用户9购买成功
用户8购买成功
用户5购买成功
# -*- coding: utf-8 -*-
# @Time : 2023/8/20 21:04
# @Author : 4C69


# 加锁处理后
from multiprocessing import Process,Lock
import random
import time
import json


# 查票
def search_ticket(name):
    with open(r'data/tickets.json', mode='rt', encoding='utf-8')as f:
        dic = json.load(f)
        print(f'用户{name}查询余票:{dic.get("tickets_num")}')


# 买票
def buy_ticket(name):
    with open(r'data/tickets.json', mode='rt', encoding='utf-8') as f:
        dic = json.load(f)
    time.sleep(random.randint(1,5))
    if dic['tickets_num'] > 0:
        dic['tickets_num'] -= 1
        with open(r'data/tickets.json', mode='wt', encoding='utf-8')as f:
            json.dump(dic, f)
        print(f'用户{name}购买成功')
    else:
        print(f'余额不足,用户{name}买票失败')


def task(name, mutex):
    search_ticket(name)
    # 加锁
    mutex.acquire()
    buy_ticket(name)
    # 释放锁
    mutex.release()

if __name__ == '__main__':
    mutex = Lock()
    for i in range(1,11):
        p = Process(target=task, args=(f'{i}', mutex))
        p.start()

        
        
# 结果
用户2查询余票:2
用户1查询余票:2
用户3查询余票:2
用户4查询余票:2
用户5查询余票:2
用户6查询余票:2
用户7查询余票:2
用户8查询余票:2
用户9查询余票:2
用户10查询余票:2
用户2购买成功
用户1购买成功
余额不足,用户3买票失败
余额不足,用户4买票失败
余额不足,用户5买票失败
余额不足,用户6买票失败
余额不足,用户7买票失败
余额不足,用户8买票失败
余额不足,用户9买票失败
余额不足,用户10买票失败

消息队列

队列:先进先出

管道+锁(数据取了一份后就没有了)

堆栈:后进先出

# -*- coding: utf-8 -*-
# @Time : 2023/8/20 21:04
# @Author : 4C69


from multiprocessing import Queue

q = Queue(6)
q.put('a')
q.put('b')
q.put('c')
q.put('d')
q.put('e')
q.put('f')
q.put('g')


v1 = q.get()
print(v1)



# 运行结果

程序会阻塞
# -*- coding: utf-8 -*-
# @Time : 2023/8/20 21:04
# @Author : 4C69


from multiprocessing import Queue

q = Queue(6)
q.put('a')
q.put('b')
q.put('c')
q.put('d')
q.put('e')
q.put('f', timeout=3)   #timeout设置超时时间,存储数据时,如果队列已经满了,3秒之内如果还是没满,也会直接报错x
q.put_nowait('g')


v1 = q.get()
print(v1)


# 运行结果
Traceback (most recent call last):
  File "C:\Users\4C69\PycharmProjects\python_basics\并发编程\消息队列.py", line 15, in <module>
    q.put_nowait('g')
  File "C:\Users\4C69\AppData\Local\Programs\Python\Python37\lib\multiprocessing\queues.py", line 129, in put_nowait
    return self.put(obj, False)
  File "C:\Users\4C69\AppData\Local\Programs\Python\Python37\lib\multiprocessing\queues.py", line 83, in put
    raise Full
queue.Full

# -*- coding: utf-8 -*-
# @Time : 2023/8/20 21:04
# @Author : 4C69


from multiprocessing import Queue

q = Queue(6)
q.put('a')
q.put('b')
q.put('c')
q.put('d')
q.put('e')
q.put('f')


v1 = q.get()
v2 = q.get()
v3 = q.get()
v4 = q.get()
v5 = q.get()
v6 = q.get()
v7 = q.get()
print(v1, v2, v3, v4, v5, v6, v7)



# 运行结果
程序会阻塞(等待v7)

# -*- coding: utf-8 -*-
# @Time : 2023/8/20 21:04
# @Author : 4C69


from multiprocessing import Queue

q = Queue(6)
q.put('a')
q.put('b')
q.put('c')
q.put('d')
q.put('e')
q.put('f')


v1 = q.get()
v2 = q.get()
v3 = q.get()
v4 = q.get()
v5 = q.get()
v6 = q.get()
v7 = q.get_nowait()
print(v1, v2, v3, v4, v5, v6, v7)


# 运行结果
C:\Users\4C69\AppData\Local\Programs\Python\Python37\python.exe C:\Users\4C69\PycharmProjects\python_basics\并发编程\消息队列.py 
Traceback (most recent call last):
  File "C:\Users\4C69\PycharmProjects\python_basics\并发编程\消息队列.py", line 23, in <module>
    v7 = q.get_nowait()
  File "C:\Users\4C69\AppData\Local\Programs\Python\Python37\lib\multiprocessing\queues.py", line 126, in get_nowait
    return self.get(False)
  File "C:\Users\4C69\AppData\Local\Programs\Python\Python37\lib\multiprocessing\queues.py", line 107, in get
    raise Empty
_queue.Empty

# -*- coding: utf-8 -*-
# @Time : 2023/8/20 21:04
# @Author : 4C69


from multiprocessing import Queue

q = Queue(6)
q.put('a')
q.put('b')
q.put('c')
q.put('d')
q.put('e')
q.put('f')
print(q.full())    # 判断队列是否已满
v1 = q.get()
v2 = q.get()
v3 = q.get()
v4 = q.get()
v5 = q.get()
v6 = q.get()
print(q.empty())    # 判断队列是否为空
print(v1, v2, v3, v4, v5, v6)


# 运行结果
True
True
a b c d e f
"""
q.put()
q.get()

# 在多进程下可能不准
q.put_nowait()
q.get_nowait()
q.full()
q.empty()
"""

进程间的通信(IPC机制)

  • 主进程和子进程之间的通信

# -*- coding: utf-8 -*-
# @Time : 2023/8/25 19:34
# @Author : 4C69


from multiprocessing import Process, Queue


def task1(q):
    q.put('宫保鸡丁')


if __name__ == '__main__':
    q = Queue()
    p = Process(target=task1, args=(q,))
    p.start()
    print(q.get())
    
    
# 运行结果
宫保鸡丁
  • 子进程和子进程之间的通信

# -*- coding: utf-8 -*-
# @Time : 2023/8/25 19:34
# @Author : 4C69


from multiprocessing import Process, Queue


def task1(q):
    q.put('宫保鸡丁')


def task2(q):
    print(q.get())

if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=task1, args=(q,))
    p2 = Process(target=task2, args=(q,))
    p1.start()
    p2.start()
    
    
    
# 运行结果
宫保鸡丁

生产消费者模型

生产者(厨师):生产或者制造数据的

消费者(顾客):消费或者处理数据的

媒介 (桌子)

# -*- coding: utf-8 -*-
# @Time : 2023/8/25 19:52
# @Author : 4C69


from multiprocessing import Process,JoinableQueue
import time
import random

'''
JoinableQueue
在Queue的基础上多了一个计数器机制,每put一个数据,计数器就加一
每调用一次task_done,计数器就减一
当计数器为0的时候,就会走q.join后面的代码
'''


def producer(name, food, q):
    for i in range(8):
        time.sleep(random.randint(1,3))
        print(f'{name}生产了{food}{i}')
        q.put(f'{food}{i}')


def consumer(name, q):
    while True:
        food = q.get()
        time.sleep(random.randint(1,3))
        print(f'{name}吃了{food}')
        q.task_done()    # 告诉队列,已经拿走了一个数据,并且已经处理完了


if __name__ == '__main__':
    q = JoinableQueue()
    p1 = Process(target=producer, args=('中华小当家', '黄金炒饭', q))
    p2 = Process(target=producer, args=('神厨小福贵', '佛跳墙', q))
    c1 = Process(target=consumer, args=('八戒', q))
    c2 = Process(target=consumer, args=('悟空', q))

    p1.start()
    p2.start()

    c1.daemon = True
    c2.daemon = True

    c1.start()
    c2.start()

    p1.join()
    p2.join()

    q.join()	# 等待队列中所有的数据被取完,计数器变成0
    # 主进程死了,消费者也要跟着陪葬,守护进程
    
    
# 运行结果
神厨小福贵生产了佛跳墙0
中华小当家生产了黄金炒饭0
神厨小福贵生产了佛跳墙1八戒吃了佛跳墙0

中华小当家生产了黄金炒饭1
悟空吃了黄金炒饭0
中华小当家生产了黄金炒饭2
中华小当家生产了黄金炒饭3八戒吃了佛跳墙1神厨小福贵生产了佛跳墙2


悟空吃了黄金炒饭1
中华小当家生产了黄金炒饭4
八戒吃了黄金炒饭2
悟空吃了黄金炒饭3
神厨小福贵生产了佛跳墙3
中华小当家生产了黄金炒饭5
八戒吃了佛跳墙2
神厨小福贵生产了佛跳墙4悟空吃了黄金炒饭4

中华小当家生产了黄金炒饭6
八戒吃了佛跳墙3
悟空吃了黄金炒饭5
神厨小福贵生产了佛跳墙5
悟空吃了黄金炒饭6
中华小当家生产了黄金炒饭7
悟空吃了佛跳墙5
八戒吃了佛跳墙4
神厨小福贵生产了佛跳墙6
悟空吃了黄金炒饭7
八戒吃了佛跳墙6
神厨小福贵生产了佛跳墙7
悟空吃了佛跳墙7

线程

进程:资源单位

线程:执行单位

把操作系统比喻成一个工厂,进程相当于工厂里的车间,线程相当于车间里的流水线。

创建进程

  • 申请内存空间 消耗资源

  • 拷贝代码 消耗资源

创建线程:在一个进程内可以创建多个线程,同一个进程内,多个线程之间的资源是共享的

  • 不需要再次申请内存空间

  • 不需要拷贝代码

创建线程的方式

# -*- coding: utf-8 -*-
# @Time : 2023/8/29 12:54
# @Author : 4C69

# 方式二
from threading import Thread
import time


def task(name):
    print(f'{name} 任务开始')
    time.sleep(1)
    print(f'{name} 任务结束')


if __name__ == '__main__':  # 创建线程可以不用放在__main__下面
    t = Thread(target=task, args=('悟空',))
    t.start()
    print('主线程')
    
    
# 运行结果
悟空 任务开始
主线程
悟空 任务结束
# -*- coding: utf-8 -*-
# @Time : 2023/8/29 12:54
# @Author : 4C69


# 方式二


from threading import Thread
import time


class MyThread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self) -> None:
        print(f'{self.name} 任务开始')
        time.sleep(3)
        print(f'{self.name} 任务结束')


if __name__ == '__main__':
    m = MyThread('悟空')
    m.start()
    print('主线程')
    
# 运行结果
悟空 任务开始
主线程
悟空 任务结束

tcp并发

# -*- coding: utf-8 -*-
# @Time : 2023/8/29 13:30
# @Author : 4C69


# tcp服务端
from multiprocessing import Process
import socket


def task(conn):
    while True:
        try:
            data = conn.recv(1024)
        except:
            break
        if not data:
            break
        print(data.decode('utf-8'))
        conn.send(data.upper())
    conn.close()


sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.bind(('127.0.0.1', 8002))
sk.listen(5)

if __name__ == '__main__':
    while True:
        conn, addr = sk.accept()
        p = Process(target=task, args=(conn, ))
        p.start()
        
  
# Windows运行后会发现报错(OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。)而mac和Linux不会,因为window创建进程时会把对应的代码以及当前进程的数据集(变量)全部都拷贝一份,创建进程的时候又创建了一个套接字,bind了相同的端口

# 解决方法:把bind放进__main__
from multiprocessing import Process
import socket


def task(conn):
    while True:
        try:
            data = conn.recv(1024)
        except:
            break
        if not data:
            break
        print(data.decode('utf-8'))
        conn.send(data.upper())
    conn.close()


sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if __name__ == '__main__':
    sk.bind(('127.0.0.1', 8080))
    sk.listen(5)
    while True:
        conn, addr = sk.accept()
        p = Process(target=task, args=(conn, ))
        p.start()
# -*- coding: utf-8 -*-
# @Time : 2023/8/29 13:06
# @Author : 4C69


# tcp客户端
import socket
import time


sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.connect(('127.0.0.1', 8080))
while True:
    sk.send(b'hello')
    data = sk.recv(1024)
    print(data.decode('utf-8'))
    time.sleep(2)

join方法

# -*- coding: utf-8 -*-
# @Time : 2023/8/30 12:33
# @Author : 4C69


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=('悟空', ))
    t.start()
    t.join()    # 主线程等待子线程运行结束之后才会往下执行
    print('主线程')
    
    
# 运行结果    
悟空 任务开始
悟空 任务结束
主线程

线程之间的数据共享

# -*- coding: utf-8 -*-
# @Time : 2023/8/30 14:08
# @Author : 4C69


from threading import Thread
import os


def task():
    print('子线程', os.getpid())


if __name__ == '__main__':
    t = Thread(target=task)
    t.start()
    print('主线程', os.getpid())
    
    
# 运行结果
子线程 9628
主线程 9628
  • 说明主线程和子线程是同一个进程内的

# -*- coding: utf-8 -*-
# @Time : 2023/8/30 14:08
# @Author : 4C69


from threading import Thread
import os


age = 18
def task():
    global age
    age = 16

if __name__ == '__main__':
    t = Thread(target=task)
    t.start()
    print(age)
    
    
# 运行结果
16
  • 说明同一个进程内,不同的线程之间数据是共享的

# -*- coding: utf-8 -*-
# @Time : 2023/8/30 14:08
# @Author : 4C69


from threading import Thread, current_thread, active_count
import os


def task():
    print(current_thread().name)    # 获取当前进程的名字(名字是自动生成的)

if __name__ == '__main__':
    t = Thread(target=task)
    t1 = Thread(target=task)
    t.start()
    t1.start()
    print(current_thread().name)
    print('活跃的线程数量', active_count())    # 统计活动的线程数量
    
    
# 运行结果
Thread-1
Thread-2
MainThread
活跃的线程数量 2

会发现一共3个进程(2个子线程,1个主线程)但它打印了2个,原因是开设线程不怎么消耗资源,所以线程起来的速度很快,同时函数里的代码非常简单,所以打印的时候第一个起来的线程已经结束了

解决方法:在函数里加个延时
from threading import Thread, current_thread, active_count
import os
import time


def task():
    print(current_thread().name)    # 获取当前进程的名字(名字是自动生成的)
    time.sleep(1)

if __name__ == '__main__':
    t = Thread(target=task)
    t1 = Thread(target=task)
    t.start()
    t1.start()
    print(current_thread().name)
    print('活跃的线程数量', active_count())    # 统计活动的线程数量
    
    
# 运行结果
Thread-1
Thread-2
MainThread
活跃的线程数量 3

守护线程

一个线程守护另一个线程,主线程死后,守护线程也会跟着死。

# -*- coding: utf-8 -*-
# @Time : 2023/8/30 14:08
# @Author : 4C69


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=('妲己', ))
    t.start()
    print('纣王驾崩')

守护线程的实现,主线程结束,子线程也跟着结束

# -*- coding: utf-8 -*-
# @Time : 2023/8/30 14:08
# @Author : 4C69


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=('妲己', ))
    t.daemon = True
    t.start()
    print('纣王驾崩')
    
  
# 运行结果
妲己还活着
纣王驾崩

例子:

# -*- coding: utf-8 -*-
# @Time : 2023/8/30 14:08
# @Author : 4C69


from threading import Thread
import time


def f1():
    print('任务1开始')
    time.sleep(1)
    print('任务1结束')


def f2():
    print('任务2开始')
    time.sleep(2)
    print('任务2结束')


if __name__ == '__main__':
    t1 = Thread(target=f1)
    t2 = Thread(target=f2)
    t1.daemon = True
    t1.start()
    t2.start()
    print('主线程')
    
    
# 运行结果
任务1开始
任务2开始
主线程
任务1结束
任务2结束

线程互斥锁

# -*- coding: utf-8 -*-
# @Time : 2023/8/30 14:08
# @Author : 4C69


from threading import Thread
import time


num = 180


def task():
    global num
    temp = num
    time.sleep(0.05)
    num = temp - 1


if __name__ == '__main__':
    l = []
    for i in range(180):
        t = Thread(target=task)
        t.start()
        l.append(t)
    for t in l:
        t.join()
    print(num)
    
    
# 运行结果
179

原因是:所有的线程都共同拿到temp然后又共同睡了0.05s,然后又共同减了1

解决方法:多个人操作一份数据,加锁处理把并发变为串行

# -*- coding: utf-8 -*-
# @Time : 2023/8/30 14:08
# @Author : 4C69
​
​
# 写法一:
from threading import Thread, Lock
import time
​
​
num = 180
​
​
def task(mutex):
    global num
    mutex.acquire()    #获取数据抢锁
    temp = num
    time.sleep(0.05)
    num = temp - 1
    mutex.release()    #数据处理完之后释放锁
​
​
if __name__ == '__main__':
    mutex = Lock()
    l = []
    for i in range(180):
        t = Thread(target=task, args=(mutex, ))
        t.start()
        l.append(t)
    for t in l:
        t.join()
    print(num)
    
    
# 写法二
num = 180
mutex = Lock()
​
​
def task():
    global num
    mutex.acquire()    #获取数据抢锁
    temp = num
    time.sleep(0.05)
    num = temp - 1
    mutex.release()    #数据处理完之后释放锁
​
​
if __name__ == '__main__':
    l = []
    for i in range(180):
        t = Thread(target=task)
        t.start()
        l.append(t)
    for t in l:
        t.join()
    print(num)
    
# 之所以可以把mutex放进上面是因为,线程之间数据共享
​
​
​

GIL全局解释器锁

https://wiki.python.org/moin/GlobalInterpreterLock?action=show&redirect=GIL

In CPython, the global interpreter lock, or GIL, is a mutex that protects access to Python objects, preventing multiple threads from executing Python bytecodes at once. The GIL prevents race conditions and ensures thread safety. A nice explanation of how the Python GIL helps in these areas can be found here. In short, this mutex is necessary mainly because CPython's memory management is not thread-safe.

在CPython中,全局解释器锁GIL是一个互斥锁,用于保护对Python对象的访问,防止多个线程同时执行Python字节码。GIL防止竞争条件并确保线程安全。关于Python GIL如何在这些领域提供帮助的一个很好的解释可以在这里找到(https://python.land/python-concurrency/the-python-gil)。简而言之,这个互斥锁是必要的,主要是因为CPython的内存管理不是线程安全的。

内存管理(垃圾回收机制)

  • 引用计数

  • 标记清除

  • 分代回收

    GC(Garbage cleaning)巡逻

Python解释器版本

  • Cpython

  • Jpython

  • Pypypython

注意:

  • GIL不是python的特点,而是Cpython解释器独有的特点

  • GIL会导致同一个进程下的多个线程不能同时执行,无法利用多核



评论