并发通信与并发池

目录

一:进程和线程相互通信

1.线程资源抢占

2.解决资源抢占

二:进程与线程的对比

1.进程和线程对比的三个方向

2.关系对比

3.区别对比

4.优缺点对比

5.总结

对列

三:消费者与生产者模型

1.线程

2.进程

四:并发池

1.线程池

2.内置池

3.使用池来实现并发服务器


一:进程和线程相互通信

进程之间有通信隔离,但是线程与线程之间没有,如果线程之间处理大量的数据的时候会出现资源抢占的情况

解决进程之间通信隔离方法:开辟一个公共空间

 mg = multiprocessing.Manager()  # 创建一个公共服务器进程
 li = mg.list()  # 开辟一个列表空间
 import multiprocessing
 ​
 var = 18
 ​
 ​
 def func(list1):
     global var
     var += 1
     print('inner', var)
     list1.append(var)
     return var
     # list1.extend([1, 2, 3, 4])
 ​
 ​
 if __name__ == '__main__':
     mg = multiprocessing.Manager()  # 创建一个公共服务器进程
     li = mg.list()  # 开辟一个列表空间
     # print(li)
     p1 = multiprocessing.Process(target=func, args=(li,))
     p1.start()
     p1.join()  # 等待子任务结束
     # print('outer', var)
     # print('outer',li)
     for i in li:
         print("outer",i)

1.线程资源抢占

 import threading
 var = 100
 ​
 ​
 def func1():
     global var
     for i in range(1000000):
         var += 1
 ​
 ​
 def func2():
     global var
     for i in range(1000000):
         var -= 1
 ​
 ​
 if __name__ == '__main__':
     t1 = threading.Thread(target=func1)
     t2 = threading.Thread(target=func2)
     t1.start()
     t2.start()
     t1.join()
     t2.join()
     print(var)

2.解决资源抢占

互斥锁

 互斥锁:对共享数据进行锁定,保证同一个时刻只能有一个线程去操作
 ​
 注意:互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁

互斥锁的使用

threading模块中定义了lock变量,这个变量本质上就是一个函数,通过调用这个函数可以获得一把互斥锁

互斥锁使用步骤

 # 创建锁
 mutex = threading.Lock()
 ​
 # 上锁
 mutex.acquire()
 ​
 # 释放锁
 mutex.release()

代码实现

 import threading
 var = 100
 ​
 # 创建互斥锁
 lock = threading.Lock()
 ​
 ​
 def func1():
     global var
     lock.acquire() # 上锁
     for i in range(1000000):
         var += 1
     lock.release() # 释放锁
 ​
 ​
 def func2():
     global var
     lock.acquire()
     for i in range(1000000):
         var -= 1
     lock.release()
 ​
 ​
 if __name__ == '__main__':
     t1 = threading.Thread(target=func1)
     t2 = threading.Thread(target=func2)
     t1.start()
     t2.start()
     t1.join()
     t2.join()
     print(var)

执行结果:

 100

说明:

通过执行结果可以看出 互斥锁能够保证多个线程访问共享数据不会出现数据错误问题

小结:

  • 互斥锁的作用 就是保证同一个时刻只能有一个线程去操作共享数据,保证共享数据不会出现错误问题

  • 使用互斥锁的好处 确保某段关键代码只能由一个线程从头到尾完整地去执行

  • 使用互斥锁会影响代码的执行效率,多任务改成了单任务执行

  • 互斥锁如果没有使用好容易出现死锁的情况

死锁

 死锁:一直等待对方释放锁的情景就是死锁

死锁的结果

会造成应用程序的停止响应,不能再处理其它的任务了

死锁实例代码:

 import threading
 import time
 ​
 # 需求:根据下标去取值,保证同一个时刻只能有一个 线程去取值
 ​
 # 创建互斥锁
 lock = threading.Lock()
 ​
 ​
 def get_value(index):
     # 上锁
     lock.acquire()
     my_list = [3, 4, 5, 6]  # 0,1,2,3
     # 判断下标释放越界
     if index >= len(my_list):
         print("下标越界", index)
         return
     # 根据下标取值
     value = my_list[index]
     print(value)
     time.sleep(0.2)
     # 释放锁
     lock.release()
 ​
 ​
 if __name__ == '__main__':
     # 模拟大量线程去执行去取值的操作
     for i in range(30):
         # 每循环一次创建一个子线程
         sub_thread = threading.Thread(target=get_value, args=(i,))
         # 启动线程执行任务
         sub_thread.start()
 ​

避免死锁

在合适的地方释放锁

 import threading
 import time
 ​
 # 需求:根据下标去取值,保证同一个时刻只能有一个 线程去取值
 ​
 # 创建互斥锁
 lock = threading.Lock()
 ​
 ​
 def get_value(index):
     # 上锁
     lock.acquire()
     my_list = [3, 4, 5, 6]  # 0,1,2,3
     # 判断下标释放越界
     if index >= len(my_list):
         print("下标越界", index)
         # 取值不成功,也需要释放互斥锁,不要影响后面的线程去取值
         # 当下标越界需要释放锁,让后面的线程还可以取值
         lock.release()
         return
     # 根据下标取值
     value = my_list[index]
     print(value)
     time.sleep(0.2)
     # 释放锁
     lock.release()
 ​
 ​
 if __name__ == '__main__':
     # 模拟大量线程去执行去取值的操作
     for i in range(30):
         # 每循环一次创建一个子线程
         sub_thread = threading.Thread(target=get_value, args=(i,))
         # 启动线程执行任务
         sub_thread.start()

小结

  • 使用互斥锁的时候需要注意死锁的问题,要在合适的地方注意释放锁

  • 死锁一旦产生就会造成应用程序的停止响应,应用程序无法再继续往下 执行了。

二:进程与线程的对比

1.进程和线程对比的三个方向

  • 关系对比

  • 区别对比

  • 优缺点对比

2.关系对比

  1. 线程是依附在进程里面的,没有进程就没有线程

  2. 一个进程默认提供一条线程,进程可以创建多个线程

3.区别对比

  1. 进程之间不共享全局变量

  2. 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法:互斥锁

  3. 创建进程的资源开销要比创建线程的资源开销要大

  4. 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位

  5. 线程不能够独立执行,必须依存在进程中

  6. 多进程开发比单进程开发稳定性要强

4.优缺点对比

  • 进程的优缺点:

    • 优点:可以用多核

    • 缺点: 资源开销大

  • 线程的优缺点:

    • 优点:资源开销小

    • 缺点:不能使用多核

5.总结

  • 进程和线程都是完成多任务的一种方式

  • 多进程要比多线程消耗的资源多,但是多进程开发比单进程开发稳定性要强,某个进程挂掉不会影响其它进程

  • 多进程可以使用CPU的多核运行,多线程可以共享全局变量

  • 线程不能单独执行 必须依附在进程里面

对列

对列:先进先出

三:消费者与生产者模型

1.线程

import threading
import queue
import time
import random

q = queue.Queue(4)


# 消费者模型
class Comsumer(threading.Thread):
    def __init__(self, q):
        super().__init__()(单继承,即只有一个父类)
        self.q = q  # 传入队列

    def run(self):
        # 消费者逻辑 不需要考虑生产者 只需要从队列中获取任务
        while True:
            res = self.q.get()
            print(f"消费者处理了数据{res},处理结果为{res * 10}")


# 生产者
class Producer(threading.Thread):
    def __init__(self, q):
        super().__init__()
        self.q = q  # 传入队列

    def run(self):
        # 生产者逻辑  不需要考虑消费者 只需要向队列中提交任务
        while True:
            time.sleep(1)
            item = random.randint(1, 100)#生成随机数
            print(f'生产者提交了任务{item}')
            self.q.put(item)


if __name__ == '__main__':
    pr = Producer(q)
    cu = Comsumer(q)
    pr.start()
    cu.start()

2.进程

import threading
import queue
import time
import random
import multiprocessing

# q = queue.Queue(4)
q = multiprocessing.Queue()  # 进程队列


# 消费者模型
class Comsumer(multiprocessing.Process):
    def __init__(self, q):
        super().__init__()
        self.q = q  # 传入队列

    def run(self):
        # 消费者逻辑 不需要考虑生产者 只需要从队列中获取任务
        while True:
            res = self.q.get()
            print(f"消费者处理了数据{res},处理结果为{res * 10}")


# 生产者
class Producer(multiprocessing.Process):
    def __init__(self, q):
        super().__init__()
        self.q = q  # 传入队列

    def run(self):
        # 生产者逻辑  不需要考虑消费者 只需要向队列中提交任务
        while True:
            time.sleep(1)
            item = random.randint(1, 100)
            print(f'生产者提交了任务{item}')
            self.q.put(item)


if __name__ == '__main__':
    pr = Producer(q)
    cu = Comsumer(q)
    pr.start()
    cu.start()
# 两种方法:1.进程队列  2。去生产一个公共空间

注意:如果是进程去实现这个的话,那一定要把进程之间的通信隔离解决

四:并发池

1.线程池

线程池:就是在一个池子里面,有多条可以重复使用的线程

import time
import queue
import threading

class MyPool:
    def __init__(self, n):
        self.queue = queue.Queue(4)
        for i in range(n): # 初始化时直接开启N条可以重复利用的线程
            threading.Thread(target=self.func,daemon=True).start()   # 保持多线程启动状态

    def func(self):
        while True:  # 不停的接收并执行任务
            # 元祖拆包
            func, args, kwargs = self.queue.get()  # 从队列中获取任务和参数
            func(*args, **kwargs)  # 执行任务
            self.queue.task_done()  # 队列计数器减一

        # 提交任务

    def submit_tasks(self, func, args=(), kwargs={}):
        self.queue.put((func, args, kwargs))  # 向队列中提交任务

    def join(self):  # 等待所有任务在还行完毕
        self.queue.join()  # 当计数器为0

def func1():
    time.sleep(2)
    print("任务一完成")


def func2(*args, **kwargs):
    time.sleep(2)
    print("任务二完成", args, kwargs)

if __name__ == '__main__':
    mp = MyPool(3)
    mp.submit_tasks(func1)
    mp.submit_tasks(func2, args=(1, 2, 'a'), kwargs={'q': 1, "w": 2})

    mp.submit_tasks(func1)
    mp.submit_tasks(func2, args=(3, 4, 'a'), kwargs={'q': 1, "w": 2})

    mp.submit_tasks(func1)
    mp.submit_tasks(func2, args=(3, 4, 'a'), kwargs={'q': 1, "w": 2})
    mp.join()

2.内置池

import time
from multiprocessing.pool import ThreadPool  # 导入内置线程池
from multiprocessing.pool import Pool  # 导入内置进程池


def func1():
    time.sleep(2)
    print("任务一完成")


def func2(*args, **kwargs):
    time.sleep(2)
    print("任务二完成", args, kwargs)


if __name__ == '__main__':
    # tp = ThreadPool(3)
    tp = Pool(3)
    tp.apply_async(func1)
    tp.apply_async(func2, args=(1, 2), kwds={'q': 1, 'w': 2})

    tp.apply_async(func1)
    tp.apply_async(func2, args=(1, 2), kwds={'q': 1, 'w': 2})

    tp.apply_async(func1)
    tp.apply_async(func2, args=(1, 2), kwds={'q': 1, 'w': 2})

    tp.close() # 停止向队列中提交任务
    tp.join() # 等待现有任务执行完毕

3.使用池来实现并发服务器

服务端:

import socket
from multiprocessing.pool import ThreadPool
from multiprocessing import Pool, cpu_count

# n = cpu_count()
# print(n)
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1', 9898))
server.listen(1000)


def socket_recv(conn):
    while True:
        recv_data = conn.recv(1024)
        if recv_data:
            print(recv_data)
            conn.send(recv_data)

        else:
            conn.close()
            break


def accept_rpocess(server):
    tp = ThreadPool(cpu_count() * 2)
    while True:
        conn, addr = server.accept()
        tp.apply_async(socket_recv, args=(conn,))


if __name__ == '__main__':
    n = cpu_count()
    pool = Pool(n)
    pool.apply_async(accept_rpocess, args=(server,))
    pool.close()
    pool.join()

客户端

import socket

client = socket.socket()
client.connect(('127.0.0.1', 9898))
for i in range(100000):
    client.send(b'ayan')
    print(client.recv(1024))

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值