day36(线程)

今日内容详细:

昨日内容回顾

如何创建进程
"""进程也可以理解成是内存中某一块存储空间"""
# 针对不同的操作系统代码创建进程的原理有区别
	windows以模块导入的方式
  linux\mac以拷贝的方式
# TCP服务端并发
 创建进程相当于新招一个服务员
代码环节需要封装两个函数 防止开设进程的时候反复执创建服务端对象

def get_server():
  server = socket.socket()
  server.bind(('127.0.0.1', 8080))
  server.listen(5)
  return server
# join方法
 主进程代码等待子进程代码运行结束之后再往后执行
#进程间数据隔离
在同一台计算机上的多个应用程序在内存中是相互隔离的(物理级别隔离)
#进程对象属性和方法
1.获取进程号
	current_process().pid
  os.getpid()
  os.getppid()
2.杀死子进程
	terminate()
3.判断进程是否存活
	is_alive()
#僵尸进程与孤儿进程
僵尸进程
	进程结束之后不会立刻清空所有信息,会短时间保留进程号等信息等待父进程回收,所有的进程都会经历僵尸进程
  父进程回收子进程资源的两种方式
  	1.父进程正常结束
    2.调用join方法
 
孤儿进程
	子进程正常运行 父进程意外死亡(主动杀死>>> taskkill\kill)
#守护进程
守护进程会随着主进程的结束立刻结束
#互斥锁
"""
并发的情况下操作同一份数据 极有可能造成数据错乱的问题
这个时候统一都会采用互斥锁的策略来解决(无论是基础阶段还是后期)

互斥锁:将并发变成串行 牺牲了效率但是保证了数据的安全

以后正常编程很少会出现让我们自己编写代码操作锁的情况!!!
"""
mutex = Lock()
mutex.acquire()  # 抢锁
mutex.release()  # 放锁

消息队列

# 由于目前的知识储备还不够直接学习消息队列 所以先学习内置队列但是我们学过队列与堆栈
"""
队列:先进先出(使用频率很高)
堆栈:先进后出(特定常见下用)
"""
"""内置队列
from multiprocessing import Queue
介绍一下此包中的常用方法:
Queue.qsize():返回队列的大小。

Queue.empty():如果队列为空,返回True,反之False。

Queue.full():如果队列满了,返回True,反之False。

Queue.get([block[, timeout]]):获取队列,timeout是等待时间;默认是阻塞,并且没有超时设置。

Queue.get_nowait():相当Queue.get(False),非阻塞。

Queue.put(item):写入队列,timeout等待时间;默认是阻塞,并且没有超时设置。

Queue.put_nowait(item):相当Queue.put(item, False),非阻塞。

Queue.task_done():在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号。

Queue.join():实际上意味着等到队列为空,再执行别的操作。"""

from multiprocessing import Queue


q = Queue(5)  # 自定义队列的长度
# 朝队列中存放数据
q.put(111)
q.put(222)
q.put(333)
print(q.full())  # False  判断队列是否满了
q.put(444)
q.put(555)
print(q.full())  # True
# q.put(666)  # 超出最大长度 原地阻塞等待队列中出现空位
print(q.get())
print(q.get())
print(q.empty())  # False  判断队列是否空了
print(q.get())
print(q.get())
print(q.get())
print(q.empty())  # True
# print(q.get())  # 队列中没有值 继续获取则阻塞等待队列中给值
print(q.get_nowait())  # 队列中如果没有值 直接报错
"""
    full()
    empty()
    get_nowait()
上述方法能否在并发的场景下精准使用???
    不能用!!!
    
之所以介绍队列是因为它可以支持进程间数据通信
"""

IPC机制(进程间通信)

"""
进程间通信意味着两个不同进程间可以交换数据,为了完成这一点,操作系统应提供两个进程可以同时访问的内存空间。
1.主进程与子进程数据交互
2.两个子进程数据交互
本质:不同内存空间中的进程数据交互
"""
from multiprocessing import Process, Queue


def producer(q):
    # print('子进程producer从队列中取值>>>:', q.get())
    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(123)  # 主进程往队列中存放数据123
    print('主进程')

在这里插入图片描述

生产者消费者模型

# 生产者
	负责生产/制作数据
# 消费者
	负责消费/处理数据
"""
比如在爬虫领域中 
	会先通过代码爬取网页数据(爬取网页的代码就可以称之为是生产者)
	之后针对网页数据做筛选处理(处理网页的代码就可以称之为消费者)

如果使用进程来演示
	除了有至少两个进程之外 还需要一个媒介(消息队列)

以后遇到该模型需要考虑的问题其实就是供需平衡的问题
	生产力与消费力要均衡
"""
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=('大厨jason', '韭菜炒蛋', q))
    p2 = Process(target=producer, args=('老板kevin', '秘制小汉堡', q))
    c1 = Process(target=consumer, args=('涛涛', q))
    c2 = Process(target=consumer, args=('龙龙', 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方法表示消费者也已经消费完数据了"""

线程理论

为什么要有线程?
细观察就会发现进程还是有很多缺陷的,主要体现在两点上:
进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
  如果这两个缺点理解比较困难的话,举个现实的例子也许你就清楚了:如果把我们上课的过程看成一个进程的话,那么我们要做的是耳朵听老师讲课,手上还要记笔记,脑子还要思考问题,这样才能高效的完成听课的任务。而如果只提供进程这个机制的话,上面这三件事将不能同时执行,同一时间只能做一件事,听的时候就不能记笔记,也不能用脑子思考,这是其一;如果老师在黑板上写演算过程,我们开始记笔记,而老师突然有一步推不下去了,阻塞住了,他在那边思考着,而我们呢,也不能干其他事,即使你想趁此时思考一下刚才没听懂的一个问题都不行,这是其二。

  现在你应该明白了进程的缺陷了,而解决的办法很简单,我们完全可以让听、写、思三个独立的过程,并行起来,这样很明显可以提高听课的效率。而实际的操作系统中,也同样引入了这种类似的机制——线程。

首先我们先来了解什么是线程?
进程由若干线程组成,一个进程至少一个线程。因此,线程于进程及其相似。进程:资源单位
  线程:执行单位
   '''一个进程中至少有一个线程'''
  """
  进程仅仅是在内存中开辟一块空间(提供线程工作所需的资源)
  线程真正被CPU执行,线程需要的资源跟所在进程的要
 线程和进程区别
 地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
调度和切换:线程上下文切换比进程上下文切换要快得多。
在多线程操作系统中,进程不是一个可执行的实体。

开设线程的两种方式

threading模块
multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性。

线程创建
from threading import Thread
import time
 
 
def func(name):
    time.sleep(2)
    print("My name is", name)
 
 # 创建线程无需在__main__下面编写 但是为了统一 还是习惯在子代码中写

if __name__ == '__main__':
    t = Thread(target=func, args=('Mike',))
    t.start()
    print("Main Thread")
结果:

Main Thread
My name is Mike

Process finished with exit code 0

当然,与进程一样,你也可以这样创建线程:

from threading import Thread
import time
 
 
class MyThread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name
 
    def run(self):
        time.sleep(2)
        print("My name is", self.name)
 
 
if __name__ == '__main__':
    t = MyThread("Mike")
    t.start()
    print("Main Thread")
结果:

Main Thread
My name is Mike

Process finished with exit code 0

线程join方法

from threading import Thread
import time
 
 
def func(name):
    time.sleep(2)
    print("My name is", name)
 
 
if __name__ == '__main__':
    t = Thread(target=func, args=("Mike",))
    t.start()
    t.join()
    print("主线程")  # 等待子线程执行完毕
    print(t.is_alive())
打印结果:
My name is Mike
主线程
False

Process finished with exit code 0

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

from threading import Thread
from multiprocessing import Process
import os
import time
 
 
def func():
    global n
    n = 0
    print(n)
 
 
if __name__ == '__main__':
    n = 100
    p = Process(target=func)
    p.start()
    p.join()
    print("主进程:", n)  # 100
    print("-------LINE-------")
    t = Thread(target=func)
    t.start()
    t.join()
    print("主线程:", n)
结果:

0
主进程: 100
-------LINE-------
0
主线程: 0

Process finished with exit code 0

线程实现TCP服务端的并发

服务端:
import threading
import socket
 
HOST = '127.0.0.1'
PORT = 8080
ADDRESS = (HOST, PORT)
BUFF_SIZE = 1024
 
ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ss.bind(ADDRESS)
ss.listen(5)
 
 
def action(conn):
    while 1:
        data = conn.recv(BUFF_SIZE)
        print(data)
        conn.send(data.upper())
 
 
if __name__ == '__main__':
    while 1:
        conn, addr = ss.accept()
        p = threading.Thread(target=action, args=(conn,))
        p.start()
 客户端:

import socket
 
sc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sc.connect(('127.0.0.1', 8080))
 
while True:
    msg = input('>>: ').strip()
    if not msg:
        continue
    sc.send(msg.encode('utf-8'))
    data = sc.recv(1024)
    print(data)

守护线程

无论是进程还是线程,都遵循:守护线程(进程)会等待主线程(进程)运行完毕后被销毁。需要强调的是:运行完毕并非终止运行。

主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束。

主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
from threading import Thread
import time
 
 
def func(name):
    time.sleep(2)
    print("My name is", name)
 
 
if __name__ == '__main__':
    t = Thread(target=func, args=('Mike',))
    t.setDaemon(True)  # 必须在start之前设置
    t.start()
    '''守护进程没有打印'''
    print("主线程")
    print(t.is_alive())
结果:

主线程
True

Process finished with exit code 0



from threading import Thread
import time
 
 
def func_one():
    print("func_one")
    time.sleep(3)
    print("func_one end")
 
 
def func_two():
    print("func_two")
    time.sleep(1)
    print("func_two end")
 
 
if __name__ == '__main__':
    t_one = Thread(target=func_one)
    t_two = Thread(target=func_two)
    '''等所有子线程结束,便会回收资源,守护线程时间太长,故而没有后面的打印'''
    t_one.setDaemon(True)
    t_one.start()
    t_two.start()
    time.sleep(0.5)
    print("主函数")
结果:

func_one
func_two
主函数
func_two end

Process finished with exit code 0

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.)
"""
1.回顾
	python解释器的类别有很多
		Cpython Jpython Ppython
	垃圾回收机制
		应用计数、标记清除、分代回收
    
GIL只存在于CPython解释器中,不是python的特征
GIL是一把互斥锁用于阻止同一个进程下的多个线程同时执行
原因是因为CPython解释器中的垃圾回收机制不是线程安全的

反向验证GIL的存在 如果不存在会产生垃圾回收机制与正常线程之间数据错乱
GIL是加在CPython解释器上面的互斥锁
同一个进程下的多个线程要想执行必须先抢GIL锁 所以同一个进程下多个线程肯定不能同时运行 即无法利用多核优势


强调:同一个进程下的多个线程不能同时执行即不能利用多核优势
	很多不懂python的程序员会喷python是垃圾 速度太慢 有多核都不能用

反怼:虽然用一个进程下的多个线程不能利用多核优势 但是还可以开设多进程!!!

再次强调:python的多线程就是垃圾!!!

反怼:要结合实际情况 
	如果多个任务都是IO密集型的 那么多线程更有优势(消耗的资源更少)
		多道技术:切换+保存状态
	如果多个任务都是计算密集型 那么多线程确实没有优势 但是可以用多进程
		CPU越多越好
	
以后用python就可以多进程下面开设多线程从而达到效率最大化
"""
1.所有的解释型语言都无法做到同一个进程下多个线程利用多核优势
2.GIL在实际编程中其实不用考虑

ps:明天代码验证多进程和多线程都有用!!!
    
# GIL全局解释器锁, 它锁的是线程
# '''
from threading import Thread,Lock
mub = 0
def f1(lock):
    with lock:
        for i in range(1000000):
                global mub
                mub += 1
    print('t1:%s'%mub)

def f2(lock):
    with lock:
        for i in range(1000000):
                global mub
                mub += 1
    print('t2:%s'%mub)
lock = Lock()
t1 = Thread(target=f1,args=(lock,))
t2 = Thread(target=f2,args=(lock,))
t1.start()
t2.start()
结果:
t1:1000000
t2:2000000

Process finished with exit code 0

线程其它方法

方法 说明
Thread.isAlive() 返回线程是否活动。
Thread.getName() 返回线程名字。
Thread.setName() 设置线程名字。
threading.currentThread() 返回当前的线程变量。
threading.enumerate() 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount() 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
import threading
import time
 
 
def func():
    time.sleep(3)
    print(threading.current_thread().getName())
 
 
if __name__ == '__main__':
    t = threading.Thread(target=func)
    t.start()
 
    print(threading.current_thread().getName())
    print(threading.current_thread())  # 主线程
    print(threading.enumerate())  # 连同主线程在内有两个运行的线程
    print(threading.active_count())  # 正在运行的线程数量
    print("主线程")
  结果:

MainThread
<_MainThread(MainThread, started 19096)>
[<_MainThread(MainThread, started 19096)>, <Thread(Thread-1, started 12764)>]
2
主线程
Thread-1

Process finished with exit code 0

金华两头乌 据说很好吃

posted @   文质彬彬赵其辉  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示