互斥锁及线程相关知识

今日内容概要

  • 多进程实现TCP服务端并发
  • 互斥锁
  • 线程相关基础知识
  • GIL全局解释器锁
  • event事件
  • 进程池与线程池
  • 协程

今日内容详细

多进程实现TCP服务端并发

多进程实现TCP服务端的并发实现的最重要部分为通过循环搭设不同的通道,实现与多个客户端的同时交互。

import socket
from multiprocessing import Process


def get_server():
    server = socket.socket()
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    return server


def get_talk(sock):
    while True:
        data = sock.recv(1024)
        sock.send(data.upper())


if __name__ == '__main__':
    server = get_server()
    while True:
        sock, addr = server.accept()
        p = Process(target=get_talk, args=(sock,))
        p.start()

互斥锁

互斥锁可以实现将多进程的并发转换为串行,互斥锁会降低程序运行的效率,但是可以提高数据操作的安全性。

互斥锁添加位置一般为数据操作之前,这样能够降低对程序运行效率的影响。

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


def search(name):
    with open(r'data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    print('%s查看票 目前剩余:%s' % (name, data.get('ticket_num')))


def buy(name):
    # 先查询票数
    with open(r'data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    # 模拟网络延迟
    time.sleep(random.randint(1, 3))
    # 买票
    if data.get('ticket_num') > 0:
        with open(r'data.json', 'w', encoding='utf8') as f:
            data['ticket_num'] -= 1
            json.dump(data, f)
        print('%s 买票成功' % name)
    else:
        print('%s 买票失败 非常可怜 没车回去了!!!' % name)


def run(name, mutex):
    search(name)
    mutex.acquire()  # 抢锁
    buy(name)
    mutex.release()  # 释放锁


if __name__ == '__main__':
    mutex = Lock()  # 产生一把锁
    for i in range(10):
        p = Process(target=run, args=('用户%s号' % i, mutex))
        p.start()

死锁现象

当一个线程内有多个互斥锁时,可能会出现死锁现象,其原因是互斥锁没有及时释放,导致锁同时占用。

信号量

在python并发编程中信号量相当于多把互斥锁。

from threading import Thread, Lock, Semaphore

sp = Semaphore(5) # 一次生成5把互斥锁

线程相关基础知识

线程概念相关

进程本质是申请一块内存空间,然后在其中执行需要执行的代码,线程则是进程中执行的某一段代码。

如果说进程是一座工厂,则线程则是工厂内部的流水线。

每个进程拥有属于自己的一个名称空间,其内部的线程可以调用名称空间中的所有名称。

创建新的线程

线程相关模块与进程类似,使用方法也类似。

from threading import Thread

常用内置方法

join() 使子线程与主线程时间同步。

current_thread() 查看当前线程的名称。

active_count() 查看当前进程内有多少个活跃的线程。

GIL全局解释器锁

概念官方解释

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.

①解释器类型:编写解释器代码所用的语言决定了解释器类型,GIL全局锁仅存在于CPython中。

②互斥锁:GIL全局解释器锁本质也是互斥锁,它的存在使CPython解释器中没有实质上的多线程。

③CPython中数据不安全,其原因为其内部的数据管理方式即垃圾回收机制。

GIL与普通互斥锁

GIL只能够确保同进程内多线程数据不会被垃圾回收机制弄乱,并不能确保程序里面的数据是否安全。GIL只针对线程执行生效,而普通互斥锁可以增加数据操作的安全性。

event事件

event事件对象相当于一个标志,初始为False,通过set使其变为True,通过wait使进程在标志为True之前阻塞。

from threading import Thread, Event

event = Event()
event.set()
event.wait()

进程池与线程池

因为硬件的发展赶不上软件,物理极限的存在使我们在编写代码的过程中不能无限制的创建进程或者线程,进程或线程过多可能会导致计算机奔溃。

进程池与线程池通过事先创建固定数量的任务量,后续只能使用事先创建的任务执行来保证计算机硬件的安全。

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import os
import time
import random
from threading import current_thread

# 1.产生含有固定数量线程的线程池
# pool = ThreadPoolExecutor(10)
pool = ProcessPoolExecutor(5)


def task(n):
    print('task is running')
    # time.sleep(random.randint(1, 3))
    # print('task is over', n, current_thread().name)
    # print('task is over', os.getpid())
    return '我是task函数的返回值'


def func(*args, **kwargs):
    print('from func')

if __name__ == '__main__':
    # 2.将任务提交给线程池即可
    for i in range(20):
        # res = pool.submit(task, 123)  # 朝线程池提交任务
        # print(res.result())  # 不能直接获取
        # pool.submit(task, 123).add_done_callback(func)

协程

协程主要是为了实现单线程下的并发,并不是一个实际存在的概念。其原理是在代码层面欺骗CPU,让CPU觉得我们的代码里面没有IO操作,实际上IO操作被我们自己写的代码检测,一旦有立刻让代码执行别的。

协程的实现主要通过gevent模块。

import time
from gevent import monkey;

monkey.patch_all()  # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn


def func1():
    print('func1 running')
    time.sleep(3)
    print('func1 over')


def func2():
    print('func2 running')
    time.sleep(5)
    print('func2 over')


if __name__ == '__main__':
    start_time = time.time()
    # func1()
    # func2()
    s1 = spawn(func1)  # 检测代码 一旦有IO自动切换(执行没有io的操作 变相的等待io结束)
    s2 = spawn(func2)
    s1.join()
    s2.join()
    print(time.time() - start_time)  # 8.01237154006958   协程 5.015487432479858

实例

import socket
from gevent import monkey;monkey.patch_all()  # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn


def communication(sock):
    while True:
        data = sock.recv(1024)
        print(data.decode('utf8'))
        sock.send(data.upper())


def get_server():
    server = socket.socket()
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    while True:
        sock, addr = server.accept()  # IO操作
        spawn(communication, sock)

s1 = spawn(get_server)
s1.join()
posted @ 2022-11-21 22:01  Akazukis  阅读(39)  评论(0)    收藏  举报