Python 并发1 进程,线程

Python 并发1: 进程,线程

相信大家在操作系统就了解过 进程,线程,死锁,系统调度等等,对基本的概念还是有的。但是,在实践的过程上我在python上的实现却略有不足。所以重新入门Python并发。

线程 Thread

所谓线程,线程是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。进程由操作系统创建,在操作系统执行进程的时候,一般会给进程分配空间等资源。而线程既可以由进程创建,也可以由操作系统创建,通过使用父进程所分配到的资源进行命令的执行调度,这里不详细展开,会在Linux入门中更新。

  • Python 中创建多线程的方法: threading. 通过 threading.Thread 创建事例对象,当start启动后即可执行线程。同时,线程的执行可能收到分时,处理器的调度策略等乱序执行。
>>> from threading import *
>>> import time
>>> def print_Thread(name = 'None'):
...     print('I am ' + name)
...     time.sleep(5)
...     print('Yes, I am ' + name)
>>> for i in range(3):
...     t = Thread(target = print_Thread, args = (str(i),))    # 这里的target是方法的调用
...     t.start()     # 生成进程,t是主线程,生成线程执行 print_Thread的进程为子线程。start之后生成子线程后主线程的逻辑结构就结束了,但是主线程会等待子线程结束后再结束.主线程如果出现问题,子线程也会死亡。
...		print('zhu_end')
I am 0
zhu_end
I am 1
zhu_end
I am 2
zhu_end
>>>
>>> Yes, I am 0
Yes, I am 1
Yes, I am 2
  • Python同时提供一些方便我们进行线程管理的API:

    threading.enumerate() 查看当前运行的线程数

>>> def print_t():
...     import time
...     time.sleep(5)
...     print('This is a threading')
>>> if __name__ == '__main__':
...     from threading import *
...     for i in range(5):
...         t = Thread(target = print_t)
...         t.start()			# 子线程在这里被创建
...     print(enumerate())
...
[<_MainThread(MainThread, started 10864)>, <Thread(Thread-6, started 21948)>, <Thread(Thread-7, started 14448)>, <Thread(Thread-8, started 5916)>, <Thread(Thread-9, started 15372)>, <Thread(Thread-10, started 15664)>]
>>> This is a threading
This is a threading
This is a threading
This is a threading
This is a threading
  • 线程代码的封装: 通过继承Thread类完成类线程的创建。这里来做一些接口的拓展:start() 每个thread 对象都只能被调用1次start() run() 如果创建Thread的子类,重写该方法。负责执行target参数传来的可执行对象。
from threading import *
import time
class Th_son(Thread):
    def __init__(self,myname:str):
        Thread.__init__(self)                # 因为__init__会覆写父进程,也就是Thread的初始化。所以要在吃实话之中重新进行弗雷德初始化来达到调用的方法
        self.myname = myname 
    def change(self,myname:str):             
        used_name = self.myname
        self.myname = myname
        print('Name from ' + used_name + ' change to ' + self.myname)
        time.sleep(1)
    def print_name(self):
        print('My name is ' + self.myname)
        time.sleep(1)
    def run(self):
        self.print_name()
#         myname = input()                    # 在python中子进程里如果调用input会出错。
        self.change('New Thread2')  
   
if __name__ == '__main__':
    New_Th = Th_son(myname = 'New Thread 1')
    print(enumerate())
    New_Th.start()                           # 因为在Python中复写了RUN, 不需要再进行复写(多态),调用Thread的方法Start()
    print(enumerate())
    
[<_MainThread(MainThread, started 5172)>]
[<_MainThread(MainThread, started 5172)>, <Th_son(Thread-12, started 3876)>]
  • 关于多线程之间的传参: 全局变量的共享

    首先明确全局变量的变化: 如果创建的可变类型变量,全局变量不需要声明global,但是可变类型由于在函数中你能找到他的地址,所以可以不用加global。 多线程间可以共享全局变量,包括使用args传参的方法。(因为太简单就不放代码了)

  • 线程的互斥锁。众所周知,资源的进程,线程的切换都是不可抗力使得结果与我们有较大偏差,因此我们引申锁得概念。这个操作系统都讲过,不再细说。 避免死锁的办法可以通过添加超时时间等方法解决

# python锁接口(互斥锁):
# 注意,因为mutex一般是对全局的阻塞, 所以mutex需要设置为全局变量OK?
mutex = threading.Lock()    # 锁创建
mutex.acquire()       		# 进行锁定
mutex.release()				# 锁释放

Python慢的深度原因: GIL

Python 慢的原因: A. 动态类型语言, 边解释边执行

​ B. GIL 无法利用多核CPU并发执行

什么是GIL: 全局解释器锁(Global Interpreter Lock , GIL) 是计算机程序设计语言解释器(CPython解释器),用于同步线程的一种机制。它使得任何时刻仅有一个线程再运行。即使在多核处理器上,使用GIL解释器也只允许同一时间执行一个线程 。依赖于IO的释放。所以IO密集型对多线程可以最大发挥优势,所以在爬虫中经常使用分布式爬虫。

在这里插入图片描述

解决GIL带来的限制,Python multiprocessing多进程机制实现真正的并行计算,利用多核CPU优势。

进程 Process

操作系统调度的基本单位 。是包括,资源,PCB,代码上下文,可执行的代码等一系列的集合。Tips: 进程与线程相比有自己分配的资源,同时也意味着进程切换开销要大很多。同时进行子进程的创建时,需要拷贝主进程的资源拷贝,时空间消耗很大。进程之间有相互通讯的模块(队列),但整体来说数据的协同没有同一进程内的线程方便。在python中,多进程可以理解为浪费了空间等资源,但达到了并发,减少了时间

  • 进程的创建:multiprocessing
    (注,在windows系统上不允许子进程使用print 打印信息,所以在这里可以用命令行,或者改用Linux系统跑代码才会有print输出)
from multiprocessing import Process
import os
from time import sleep

# 子进程要执行的代码
def run_proc(name, age, **kwargs):
    for i in range(10):
        #在Win下的python环境中,子进程是无法进行Print打印的,所以我们采取的方法最好就是用Linux的虚拟机啦,从这个代码之后所有的代码是虚拟机版本
        print('子进程运行中,name= %s,age=%d ,pid=%d...' % (name, age,os.getpid()))
        print(kwargs)
        sleep(0.5)

if __name__=='__main__':
    print('父进程 %d.' % os.getpid())
    p = Process(target=run_proc, args=('test',18), kwargs={"m":20})
    print('子进程将要执行')
    p.start()
    sleep(1)
    p.terminate()
    p.join()
    print('子进程已结束')

C:\Users\40629>python C:\Users\40629\Desktop\Untitled-1.py
父进程 19680.
子进程将要执行
子进程运行中,name= test,age=18 ,pid=18484...
{'m': 20}
子进程运行中,name= test,age=18 ,pid=18484...
{'m': 20}
子进程已结束
  • 子进程继承父进程的资源,同时继承父进程的环境变量
>>> from multiprocessing import *
>>> import os
>>> def show():
...     try:
...         print(os.environ['NewProcessComming'])
...     except:
...         print('Not success')
...
>>> if __name__ == '__main__':
...     os.environ['NewProcessComming'] = '/bin/bash'
...     p1 = Process(target = show)
...     p1.start()
...
>>> /bin/bash
# 重新进入PYTHON, 新开一个进程
>>> import os
>>> os.environ['NewProcessComming']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/os.py", line 675, in __getitem__
    raise KeyError(key) from None
KeyError: 'NewProcessComming'
# 环境变量资源没有保存哦,说明上面子进程确实是继承了父进程的资源,也会导致伊西俄安全问题
  • 进程间的通信,通过操作系统来实现(SOCKET),一般是采用队列的数据结构存储数据与进行数据的交换: Queue
# 基础API:
q= mulyiprocessing.Queue(space)      		# 队列对象的实例化.Tips:q在多线程通讯之间需要以传参的方式传递才能发挥作用
q.put()						  				# 存数据,在python中数据类型任意。如果Q队列满了,就会阻塞等待
q.get() 					  				# 取数据,如果取数据的时候队列为空,就会阻塞等待
q.full()					  				# 判断是否满
q.empty()          			  				# 是否空

实例Show: (扩展,在不同主机间网络通讯实现消息队列,Redis, Kafka)

>>> from multiprocessing import *
>>> import time
>>> def read(queue):
...     while(True):
...         if queue.empty():
...            time.sleep(1)
...         m = queue.get()
...         print('Done Reading ' + str(m))
>>> def write(queue):
...     write_num = 0
...     while(True):
...         if queue.full():
...            time.sleep(1)
...         queue.put(write_num)
...			print('Done Writing ' + str(write_num))
...         write_num += 1
>>> if __name__ == '__main__':
...     queue = Queue(3)
...     p1 = Process(target = read, args = (queue,))
...     p2 = Process(target = write, args = (queue,))
...     p1.start()
...     p2.start()

Done Writing 0
Done Writing 1
Done Writing 2
Done Reading 0
Done Reading 1
.......
  • 进程池:当创建的进程目标成千上万,创建进程工作量巨大,同时创建,回收的代价太高,因此考虑multiprocessing.Pool,也就是进程池

初识化进程池时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没满就会创建一个新的进程执行改请求。但是如果进程池被取空了,请求就会被阻塞知道有进程空出来。

from multiprocessing import Pool
import os
from time import sleep
def test():
    print("I'm Process " + str(os.getpid()))

if __name__ == '__main__':
    process_pool = Pool(3)
    for i in range(5):
        process_pool.apply(test)
    
    # 先关闭进程池在等待所有进程结束
    process_pool.close()
    # 阻塞主进程
    process_pool.join()
    print('___end____')
    
I'm Process 15824
I'm Process 15824
I'm Process 8876 
I'm Process 12408
I'm Process 15824
___end____

可以发现进程号相同,换句话说进程池减少了进程创建和销毁的过程.

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值