线程微讲解

线程微讲解

1.消息队列

队列:先进先出(使用频率很高)
堆栈:先进后出(特定场景下才会使用)
	以后我们会直接使用别人封装好的消息队列来实现各种数据传输,我们先来看一个别人封装好的消息队列,也就是内置队列。
    from multiprocessing import Queue

    q = Queue(5)  # 自定义队列长度
    # 朝队列中存放数据
    q.put(1)
    q.put(2)
    q.put(3)
    print(q.full())  # Fales 判断队列是否满了,满了就返回True,否则就返回Fales
    q.put(4)
    q.put(5)
    print(q.full())  # True
    # q.put(6)  # 超出最大长度,原地阻塞等待队列出现空位
    print(q.get())
    print(q.get())
    print(q.get())
    print(q.empty())  # False  判断队列是否空了,空了就返回True,否则就返回False
    print(q.get())
    print(q.get())
    print(q.empty())  # True
    # print(q.get())  # 队列中没有值了还要继续取值的话就会原地等待队列添加新的值
    # print(q.get_nowait())  # 队列中如果没有值了就会直接报错
'''full()、empty()、get_nowait()这三种方法在并发的场景下是不能精准使用的,之所以介绍队列是因为它可以支持进程间数据通信'''

2.IPC机制(进程间通信)

# 进程间通信有两种方式
    1.主进程与子进程数据交互
    2.两个子进程数据交互
# 进程间通信的本质
	进程间通信的本质就是不同内存空间中的进程数据交互。
# 代码演示
from multiprocessing import Process,Queue
def producer(q):
    print('子进程producer从队列中取值',q.get())  # 第一次取值,取出的是111
    q.put('子进程producer从队列中添加值')  # 再次添加值

def consumer(q):
    print('子进程consumer从队列中取值',q.get())  # 这次取值取出的是第二次添加的值,因为第一次添加的已经被取出了

if __name__ == '__main__':
    q = Queue()
    p = Process(target=producer,args=(q,))
    p1 = Process(target=consumer,args=(q,))
    p.start()
    p1.start()
    q.put(111)  # 往队列中先添加一个值
    print('主进程')

3.生产者消费者模型

# 生产者
	制作、产生数据(负责生产)
# 消费者
	处理、回收数据(负责消费)
'''用进程来演示,至少需要两个进程,还需要一个媒介,也就是消息队列,该模型需要考虑的问题就是供需平衡的问题,也就是生产
的要跟得上消费的,消费的要追得上生产的'''
# 代码演示
from multiprocessing import Process,Queue,JoinableQueue
import time
import random

def producer(name,food,q):
    for i in range(5):
        data = f'{name}生产的{food}{i}'
        print(data)
        time.sleep(random.randint(1,3))  # 模拟产生过程
        q.put(data)

def consumer(name,q):
    while True:
        food = q.get()
        if food == None:
            print('没饭吃了')
            break
        time.sleep(random.random())
        print(f'{name}吃了{food}')
        q.task_done()  # 每次吃完数据必须给队列返回一个信息

if __name__ == '__main__':
    # q = Queue()
    q = JoinableQueue()
    p1 = Process(target=producer,args=('oscar','糖醋排骨',q))
    p2 = Process(target=producer,args=('jason','土豆炖排骨',q))
    c1 = Process(target=consumer,args=('tony',q))
    c2 = Process(target=consumer,args=('tom',q))
    c1.daemon = True
    c2.daemon = True
    p1.start()
    p2.start()
    c1.start()
    c2.start()
    # 生产者生产完所有数据之后,往队列中添加结束的信号
    p1.join()
    p2.join()
    # q.put(None)  # 结束信号的个数要跟消费者个数一样
    # q.put(None)  # 结束信号的个数要跟消费者个数一样
    '''队列中其实已经自己加了锁,所以多进程取值也不会冲突,并且取走就没了'''
    q.join()  # 等待队列中数据全部被取出,一定要让生产者全部结束才能判断正确
'''执行完上述的join方法表示消费者也已经消费完数据了'''

4.线程理论

# 什么线程
	进程:资源单位
	线程:执行单位
'''进程仅仅是在内存中开辟一块空间(提供线程工作所需的资源),线程是真正被CPU执行的,线程需要的资源就跟所在进程要,所
以一个进程中至少有一个线程'''
# 为什么有线程
	因为开设线程的消耗远远小于进程,开进程需要申请内存空间,还需要拷贝代码,而一个进程内就可以开设多个线程,无
需申请内存空间,也无需拷贝代码,因为一个进程内的多个线程数据是共享的。

5.开设线程的两种方式

from threading import Thread
import time

# def task(name):
#     print(f'{name}刚刚来')
#     time.sleep(3)
#     print(f'{name}又走了')
# 
# # 创建线程无需在__main__子代码中,但是为了同一进程,还是习惯写在里面
# if __name__ == '__main__':
#     t = Thread(target=task,args=('oscar',))
#     t.start()
#     print('主线程')

class MyThread(Thread):
    def __init__(self,username):
        super().__init__()
        self.username = username
    
    def run(self):
        print(f'{self.username}刚刚来了')
        time.sleep(3)
        print(f'{self.username}又走了')
        
if __name__ == '__main__':
    t = MyThread('oscar')
    t.start()
    print('主线程')

6.线程join方法

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=('oscar',))
    t.start()
    t.join()  # 主线程等待子线程结束之后才会结束
    print('主线程')
'''主线程等待子线程结束之后结束,是因为主线程结束标志着进程的结束,所以主线程等待子线程结束之后结束是为了确保子线程
运行过程中所需要的资源'''

7.同一个进程内的多个线程数据共享

from threading import Thread

s = 1
def stak():
    global s
    s = 0

if __name__ == '__main__':
    t = Thread(target=stak)
    t.start()
    t.join()
    print(s)  # 0

8.线程对象属性和方法

# 1.验证一个进程下的多个进程是否真的处于一个进程
# 2.统计进程下活跃的线程数
	active_count()  # 主线程也算
# 3.获取线程名字
	1.current_thread().name
    	返回的MainThread是主线程,Thread-加数字的是子线程
	2.self.name
# 代码演示
from threading import Thread, active_count, current_thread
import os
import time

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())

9.守护线程

from threading import Thread
import time

def task(name):
    print(f'{name}刚刚来')
    time.sleep(3)
    print(f'{name}又走了')

if __name__ == '__main__':
    t1 = Thread(target=task,args=('oscar',))
    t2 = Thread(target=task,args=('jason',))
    t1.daemon = True
    t1.start()
    t2.start()
    print('主线程')
'''主线程要等待所有的子线程结束之后才会结束'''

10.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解释器中,不是python的特征。
	GIL是一把互斥锁用于阻止同一个进程下的多个线程同时执行,原因是因为CPython解释器中的垃圾回收机制不是线程安
全的。
	GIL是加在CPython解释器上面的互斥锁
同一个进程下的多个线程要想执行必须先抢GIL锁 所以同一个进程下多个线程肯定不能同时运行 即无法利用多核优势。
	虽然同一个进程下的多个线程不能同时执行即不能利用多核优势,但是可以开设多个进程,结合实际情况,如果多个任
务都是IO密集型的,那么多线程更有优势(消耗的资源更少),如果多个任务都是计算密集型,那么多线程确实没有优势,但是可
以用多进程。
	所有的解释性语言都无法做到同一个进程下多个线程利用多核优势。'''

这里是IT小白陆禄绯,欢迎各位大佬的指点!!!

posted @ 2022-04-20 23:57  陆禄绯  阅读(28)  评论(0)    收藏  举报