python 并发编程之线程

一、进程队列和线程队列

1、进程队列的使用

from multiprocessing import Queue

python中内置的有一个Queue类,进程之间的数据是隔离的,所以,我们使用了队列来实现了进程之间的通信

from multiprocessing  import Queue

if __name__ == '__main__':
    q = Queue(3)  # 队列的大小默认很大
    # 1. 如何入队、
    """obj, block=True, timeout=None"""
    q.put('hellowrold1')
    q.put('helloworld2')
    q.put('helloworld3')
    # q.put('helloworld4', block=False)  # 如果你知道了block参数,当队列满的时候,放不进去的时候,直接报错
    # q.put('helloworld4', timeout=3)  # 如果你知道了timeout参数,当往队列中放入数据的时候,等待3秒,如果放不进去,直接报错
    # q.put_nowait('helloworld4')  # 往队列中放入数据的时候,放不进去直接报错, 指定了参数:block=False

    # 2. 出队
    print(q.get())
    print(q.get())
    # print(q.get())
    """ block=True, timeout=None"""
    # print(q.get(block=False))
    # print(q.get(timeout=3))

    # print(q.get_nowait())  # 不等待取,取不到就报错
    print(q.qsize())  # 队列中剩余的数据量, 这个方法的结果有点不准确.
    # print(q.empty()) # 判断队列是否为空
    # print(q.full())  # 判断队列是否满了 

2、线程队列

import queue

线程之间的数据是共享的,那么,我们为什么还使用队列呢?

队列的底层本质是:管道 +  信号量(锁)

锁就是为了保证数据的安全

线程内部使用队列,也是为了保证线程里的数据安全

信号量的补充:

信号量(Semaphore)是一种同步原语,用于控制对共享资源的访问。信号量通常用于限制同时访问某个资源的线程或进程的数量。

Semaphore(initial_value) 中initial_value的值表示允许同时访问共享资源的线程或进程的数量

from threading import Semaphore  # 导入类

semaphore = Semaphore(initial_value)  # 实例化一个信号量

常用的方法包括:

  • acquire(): 获取一个许可,如果没有可用的许可,则线程或进程将被阻塞,直到有可用的许可为止。
  • release(): 释放一个许可,使其可供其他线程或进程使用。
  • __enter__()__exit__()方法:可以使用with语句来自动获取和释放许可。

先进先出

import queue

q = queue.Queue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())

先进后出 queue.LifoQueue()

import queue
# Lifo: last input first output
q = queue.LifoQueue()  # 得到一个对象,队列
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())

优先级 queue.PriorityQueue()

import queue

q = queue.PriorityQueue()
# put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put(('10', 'a'))
q.put(('20', 'b'))
q.put(('30', 'c'))

print(q.get())
print(q.get())
print(q.get())

输出结果(数字越小优先级越高,优先级高的优先出队):
('10', 'a')
('20', 'b')
('30', 'c')

消费者和生产者使用线程队列案例:

import threading
import queue

# 生产者线程函数,将数据放入队列
def producer():
    for i in range(5):
        item = f"Item {i + 1}"
        my_queue.put(item)
        print("Produced", item)

# 消费者线程函数,从队列中取出数据
def consumer():
    while True:
        item = my_queue.get()
        if item is None:
            break
        print("Consumed", item)
        my_queue.task_done()

# 创建一个线程队列
my_queue = queue.Queue()

# 创建并启动生产者线程
producer_t = threading.Thread(target=producer)
producer_t.start()

# 创建并启动消费者线程
consumer_t = threading.Thread(target=consumer)
consumer_t.start()

# 等待生产者线程完成
producer_t.join()

# 等待队列中的所有任务完成
my_queue.join()

# 向队列发送结束信号
my_queue.put(None)

# 等待消费者线程完成
consumer_t.join()

print('运行结束!')
'''
Produced Item 1
Produced Item 2
Produced Item 3
Produced Item 4
Produced Item 5
Consumed Item 1
Consumed Item 2
Consumed Item 3
Consumed Item 4
Consumed Item 5
运行结束
'''

二、 生产者消费者模型

1、在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题,该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

2、多生产者,少消费者

from multiprocessing  import Process, Queue
import  time

def producer(q, name, food):
    for i in range(10):
        q.put('生产者:%s,生产了第%s个 %s' % (name, i, food))
        # time.sleep(0.3)

def consumer(q):
    while True:
        res = q.get()

        if res is None:
            break
        print(res)


if __name__ == '__main__':
    q = Queue(20)

    # 四个生产者
    p1 = Process(target=producer, args=(q, 'kevin', '包子'))
    p2 = Process(target=producer, args=(q, 'tank', '馒头'))
    p3 = Process(target=producer, args=(q, 'json', '豆汁'))
    p4 = Process(target=producer, args=(q, 'zjz', '手抓饼'))

    p1.start()
    p2.start()
    p3.start()
    p4.start()

    # 两个消费者
    p5 = Process(target=consumer, args=(q,))
    p6 = Process(target=consumer, args=(q,))
    p5.start()
    p6.start()

    p1.join()
    p2.join()
    p3.join()
    p4.join()
    # 保证四个生产者先执行完再放入None。

    # 生产者放入2个None标志,2个消费者取到None则退出程序。
    # 在windos中拉起一个子进程的速度可能较慢一点,主进程的q.put(None)较快,直接推None到队列中,消费者取到退出
    q.put(None)
    q.put(None)

3、多消费者,少生产者

消费者之间会产生消费竞争,先抢到先消费,直至拿到None退出

from multiprocessing  import Process, Queue

def producer(q, name, food):
    for i in range(1, 11):
        q.put('生产者:%s,生产了第%s个 %s' % (name, i, food))
        # time.sleep(0.3)

def consumer(q):
    while True:
        res = q.get()
        # res = q.get_nowait()   #  一有等待就报错
        # res = q.get(block=False)  # 一有等待就报错
        # get()方法,用于从队列中获取项目。通过传递block = False参数,可以将get()方法设置为非阻塞模式。
        # 当调用get()方法并将block = False作为参数时,它会立即尝试从队列中获取一个项目.如果队列为空,它会立即返回,并引发queue.Empty异常。

        if res is None:
            break
        print(res)

if __name__ == '__main__':
    q = Queue(20)

    # 两个生产者
    p1 = Process(target=producer, args=(q, 'kevin', '包子'))
    p2 = Process(target=producer, args=(q, 'tank', '馒头'))
    # p3 = Process(target=producer, args=(q, 'json', '豆汁'))
    # p4 = Process(target=producer, args=(q, 'zjz', '手抓饼'))

    p1.start()
    p2.start()

    # 四个消费者
    p3 = Process(target=consumer, args=(q,))
    p4 = Process(target=consumer, args=(q,))
    p5 = Process(target=consumer, args=(q,))
    p6 = Process(target=consumer, args=(q,))

    p3.start()
    p4.start()
    p5.start()
    p6.start()

    p1.join()
    p2.join()

    # 生产者放入2个None标志,2个消费者取到None则退出程序。
    # 在windos中拉起一个子进程的速度可能较慢一点,主进程的q.put(None)较快,直接推None到队列中,消费者取到退出
    q.put(None)
    q.put(None)
    q.put(None)
    q.put(None) 

三、线程

1、理论

  1. 进程是一个任务的执行过程,在这个过程中实际做事的是线程,线程是开在进程里面的,需要先有进程,再有线程,一个进程中至少有一个线程,当然,也可以有多个线程。
  2. 进程是资源分配的基本单位,线程是CPU执行的最小单位
  3. 进程和线程都是有操作系统来调度的,协程它是有程序员来调度的。
  4. 进程 >>> 线程 >>> 协程
  5. 进程资源消耗是最多的 >>> 线程的资源 >>> 协程的

2、开启单线程

from threading   import  Thread

def task():
    print('task started!')

if __name__ == '__main__':  # 这不是必须的,但是尽量跟进程一样写
    t = Thread(target=task)
    t.start()

3、守护线程

子线程睡眠2秒,主线程执行完了,子线程也跟着退出,没有来得及打印子线程的代码。

from threading  import Thread
import time
def task(a, b, c, name):
    time.sleep(2)
    res = a + b + c
    print(t.name)
    print(res, name)

if __name__ == '__main__':
    t = Thread(target=task, name='this is thread_name', args=(1, 2, 3), kwargs={'name': 'a'})
    # t.daemon = True # 主线程结束,子线程结束
    t.setDaemon(True)  # 主线程结束,子线程结束
    t.start()
    # t.join()
    print(t.name)  # 子线程的名字, 等价于 print(t.getName())

    # 守护线程
    print('这是主线程代码')

4、开启多线程

from  threading   import  Thread

def task(i):
    print('threading started!', i)

if __name__ == '__main__':
    tt = []
    for i in range(5):
        t = Thread(target=task, args=(i,))
        t.start()
        tt.append(t)
    for j in tt:
        j.join()
    print('123')

5、进程和线程的比较

1. 进程的开销远远大于线程的开销
2. 进程之间的数据是隔离的,线程之间的数据呢? 线程之间的数据是共享的,严格的来说:同一个进程下的线程之间的数据是共享的
3. 想让不同进程之间的线程之间的数据共享------->还是让进程之间通信------->线程之间也通信了--->队列

四、多进程多线程案例

from multiprocessing  import  Process
from  threading import Thread

# 线程任务
def perform_thread_task(task_name):
    print("Thread task", task_name)

    # 这里可以添加具体的任务逻辑
    print("Thread task", task_name, "completed")

# 进程任务
def perform_process_task(task_name):
    print("Process task", task_name)

    # 这里可以添加具体的任务逻辑
    print("Process task", task_name, "completed")


if __name__ == "__main__":
    # 创建并启动多线程
    for i in range(3):
        thread = Thread(target=perform_thread_task, args=(f"Thread-{i+1}",))
        thread.start()

    # 创建并启动多进程
    for i in range(3):
        process = Process(target=perform_process_task, args=(f"Process-{i+1}",))
        process.start()

# 输出结果
# Thread task Thread-1
# Thread task Thread-1 completed
# Thread task Thread-2
# Thread task Thread-2 completed
# Thread task Thread-3
# Thread task Thread-3 completed
# Process task Process-1
# Process task Process-1 completed
# Process task Process-2
# Process task Process-2 completed
# Process task Process-3
# Process task Process-3 completed

  

 

posted @ 2023-07-06 16:01  凡人半睁眼  阅读(30)  评论(0编辑  收藏  举报