python线程

注:线程和进程在方法大致相同

一.IPC机制

什么是IPC机制:
1.主进程与子进程进行数据交互
2.两个子进程之间的数据交互

1.主进程与子进程进行数据交互

from multiprocessing import Process, Queue
# 模块方法调用了队列方法
def producer(q):
    print('子进程我准备去主进程中取值我的主队列为q', q.get())


if __name__ == '__main__':
    q = Queue()
    # 调用了队列的方法
    p = Process(target=producer, args=(q, ))
    p.start()
    q.put(123)
    # 主进程往队列中存放数据123
    print('主进程')
"""
创建一个进程并通过队列的方法在主进程传值
通过子进程使用队列的方式进行取值
结果
主进程
子进程我准备去主进程中取值我的主队列为q 123
"""

2.两个子进程之间的数据交互

from multiprocessing import Process, Queue

def producer(q):
    q.put('我是子进程1要去列队里添加值')


def consumer(q):
    print('我是子进程2我要去取子进程1', q.get())


if __name__ == '__main__':
    q = Queue()
    w = Process(target=producer,args=(q,))
    w1 = Process(target=consumer, args=(q,))
    w.start()
    w1.start()
    print('我是主进程')

"""
同上,也可以通过两个子进程一个传入一个接纳方式进行,数据的交互
""'

二.生产者消费者模型

# 生产者
负责生产与制作数据

# 消费者
负责消费与处理数据

1.使用进程实现

from multiprocessing import Process,JoinableQueue
# 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()
        time.sleep(random.random())
        print(f'{name}吃了{food}')
        q.task_done()
        # 每次去完数据必须给队列一个反馈


if __name__ == '__main__':
    q = JoinableQueue()
    p1 = Process(target=producer, args=('jason', '木桶饭', q))
    p2 = Process(target=producer, args=('kevin', '红烧排骨', q))
    c1 = Process(target=consumer, args=('qqq', q))
    c2 = Process(target=consumer, args=('zzz', q))
    c1.daemon = True
    c2.daemon = True
    # 将子进程设置成守护进程
    # 那么主进程结束后子进程跟着结束
    p1.start()
    p2.start()
    c1.start()
    c2.start()
    # 生产者生产完所有数据之后 往队列中添加结束的信号
    p1.join()
    p2.join()
    # 子进程join方法将子进程结束后执行主进程
    q.join()
    # 运行到这一步时候就因为子进程和主进程同时结束了所以
    # 等待队列中数据全部被取出(一定要让生产者全部结束才能判断正确)

三.线程

1.进程:程序应不能单独运行,只有将程序装在到内存中,系统为他分配资源才能运行

2.进程与程序的区别在于:程序是'死的'而进程是活的是根据程序的启动而进行

3.多道技术:多道技术本质是提升CPU的利用效率,多个程序加载到内存当中,在操作系统的调度下,实现并发'一并启动'

4.进程的缺陷:他在内存中创建了一个空间,内部有进程的子代码如果碰到io'其他操作的话'就会挂起 从新开始执行'如果想实现既可以进行io操作又不影响其他进程的子代码运行那么就引出了线程'在io操作下从新开始执行那么期间就会浪费CPU的资源

5.线程:因进程因为浪费资源,而多进程就更加浪费资源了
所以就开发出了 能够独立运行的基本单位-线程
eg:进程是资源分配的最小单位.线程是cpu调度的最小单位
每一个进程当中.最起码要有一个线程

6.可以这么举例子'如果把进程当做一个工厂的厂房,那么线程就是厂房内的工人,在厂房内肯定有其他公共的设施,设备或者是工具,那么这些工人(线程)就可以共享这些工具设施(资源)'
# 1.可以引出:如果两个厂房(进程)那么他 厂房与厂房之间的线程是不通用的(工人怎么能共享呢)
# 2. 一个线程只能属于一个进程

四.启动线程两种方式

"""
线程执行消耗较小,速度要快
启动线程和启动进程格式是一样的
但是线程可以不需要写__main__方法
更换了模块threading和方法Thread
"""

1.创建函数方式

import time
from threading import Thread
# 更换了模块和方法调用模块
# 定义函数 接受行参 name
def task(name):
    print('%s  这个是子线程' % name)
    time.sleep(2)
    print('%s  子线程2号' % name)
# 创建线程
j = Thread(target=task, args=('thn',))
j.start()
print('这个是线程主线程')
"""
和进程一样先执行了主线程,在执行了子线程
结果也是不同的,因为创建线程的消耗很小
执行的时候速度超过了执行主线程的消耗
结果是
子进程 thn
这是主进程
子进程2号 thn
"""

2.创建类(面向对象)方式

import time
from threading import Thread

# 定义面向对象 创建一类继承Thread
class MyProcess(Thread):
    def __init__(self, name):
        super().__init__()                 #  self.name = name
        self.name = name                   #  super().__init__()
                                           # 唯一不同点在于这

    def run(self):
        print('子进程',self.name)
        time.sleep(2)
        print('子进程2号',self.name)


j = MyProcess('thn')
j.start()
print('这是主进程')
"""
也是和类创建进程一样,上方不同点是因为不能从新执行实例化所以需反过来调用
其他的基本一致
结果是
子进程 thn
这是主进程
子进程2号 thn
"""

五.线程join方法

1.线程join方法与进程join方法一致如下

1.线程join方法

import time
from threading import Thread
# 定义函数 接受行参 name
def task(name):
    print('%s  这个是子线程' % name)
    time.sleep(2)
    print('%s  子线程2号' % name)


# 创建线程
j = Thread(target=task, args=('thn',))
j.start()
j.join()
print('这个是线程主进程')
"""
join方法就是要先执行子线程,子线程执行完毕后执行主线程,主线程进行回收
"""

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

from threading import Thread

x = 99

def task():
    global x
    x = 333
    print(x)
# 1.本来只有在调用阶段才能 修改全局变量打印333


if __name__ == '__main__':
    p = Thread(target=task)
    p.start()
    p.join()
    print(x)

# 2.线程可以直接使用函数调方法修改了x
"""
上述也提过,进程算是一个工厂,线程就是工厂里上班的工人,内部公共工具,公共设施,等都是公用的
"""

七.线程对象属性和方法

# 1.验证主线程与子线程是在一个内存里
通过os.getpid()

# 2.验证在内存里有几个线程包含主线程与子线程
通过方法 activeCount()

# 3.获取内存里主线程名字与有子线程名字
通过方法 current_thread().name 获取主线程名字 与子线程名字 

1.验证主线程与子线程是在一个内存里

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


j = Thread(target=task)
j.start()
j.join()
print('这个是线程主进程',os.getpid())
"""
子线程获进程号 6096
这个是线程主进程 6096
一致的
"""

2.验证在内存里有几个线程包含主线程与子线程

from threading import Thread, active_count

import time

# 定义函数 接受行参 name
def task():
    time.sleep(3)
    print('子线程获进程号')


j = Thread(target=task)
j.start()
print(active_count())
"""
获取到1个子线程与1个主线程
结果是2
"""

3.获取内存里主线程名字与有子线程名字

def task():
    time.sleep(3)
    print('子线程获进程号',j.name)  # 获取子线程名字


j = Thread(target=task)
j.start()
print(current_thread())            # 获取主线程名字
"""
<_MainThread(MainThread, started 2316)>
子线程获进程号 Thread-1
"""

八.守护线程

from threading import Thread
import time

def tack(name):
    print(f'子线程{name}')
    time.sleep(3)
    print(f'子线程2{name}')

p = Thread(target=tack,args=('哎',))
p.daemon = True  # 将子线程序设置为守护线程,主线程序接受后 子线程序立即结束
p.start()
print('主线程')
"""
结果
子进程哎
主线程
为什么没有子线程2呢 因为子线程1抢先在 主线程前跑了不然子线程1都看不到,
"""

九.GIL全局解释器锁

Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。

对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

在多线程环境中,Python 虚拟机按以下方式执行:

设置 GIL;
切换到一个线程去运行;
运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));
把线程设置为睡眠状态;
解锁 GIL;
再次重复以上所有步骤。
在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL
posted @ 2022-04-20 22:47  笑舞狂歌  阅读(51)  评论(0)    收藏  举报