python-线程-进程-协程

进程和线程:
线程,是计算机中可以被cpu调度的最小单元(真正在工作)。 进程,是计算机资源分配的最小单元(进程为线程提供资源)。

一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源。

一、多线程

1.创建一个简单的线程

以下代码中,创建了两个线程,name参数可以设置线程名(要在线程执行前设置),target参数传入函数名(必传),t1.start()是启动线程的方法

import threading

def func1():
	time.sleep(1)
    print(f'hello func1 !')
    
def func2():
	time.sleep(1)
    print('hello func2 !')
   
t1 = threading.Thread(target=func1, name="线程1")
t2 = threading.Thread(target=func2, name="线程2")

print(t1.name)
print(t1.name)

t1.start()
t1.start()

2.自定义线程类

自定义线程类,直接将线程需要做的事写到run方法中。

import threading
  
class MyThread(threading.Thread):
	def __init__(self, name):
		self.name = name
		
    def run(self):
		print('执行此线程', n)
		
t = MyThread(args=(100,))
t.start()

3.线程传参

线程传参有两种方法,一种是元组(元组中如果只有一个参数是后面要加逗号不然会报错),另一种是字典方法。

import threading

def func1(m, n):
    print(f'm = {m}')
    print(f'n = {n}')
    
def func2(m, n):
    print(f'm = {m}')
    print(f'n = {n}')

t1 = threading.Thread(target=func1, args=(1, 2))
t2 = threading.Thread(target=func2, kwargs={'m': 1, 'n': 2})

t1.start()
t2.start()

4.线程阻塞,守护线程

t1.join()为线程阻塞,作用是等t1线程执行完毕后主线程才会继续往下走
t1.setDaemon(True)-必须写在.start()前,设置守护线程,参数为True设置为守护线程,主线程执行完毕后子线程会自动结束
#参数为False设置为非守护线程,主线程等待子线程执行完毕后,主线程线程才会结束(不设置默认为False)

import threading

def func1():
    time.sleep(1)
    print(f'hello func1 !')

def func2():
    time.sleep(1)
    print(f'hello func2 !')

t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)

t1.setDaemon(True)  # 设置为守护线程,主线程执行完毕后子线程会自动结束
t1.start()
t2.start()

t1.join()  # t1线程执行完毕才会继续往下走
t2.join()  # t1线程执行完毕才会继续往下走

5.GIL锁

锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁。

同步锁: 给一段代码加了同步锁之后,在这段代码执行时只能有一个线程执行。–不支持嵌套,在一个函数内只能锁一次–如果嵌套会造成死锁

递归锁:支持多次申请和释放–支持嵌套锁

死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且 同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。

import threading

# lock = threading.Lock()  # 同步锁
lock = threading.RLock()  # 递归锁

def func1():
    look.acquire()  # 加锁 第一个抵达的线程并上锁其他线程再此等待
    time.sleep(1)
    print(f'hello func1 !')
    look.release()  # 解锁 解锁后其他线程可以进入并执行

def func2():
    lock.acquire()  # 加锁
    time.sleep(1)
    print(f'hello func2 !')
    lock.release()  # 解锁
    
t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)

t1.start()
t2.start()

另一种加锁方法with–with下的程序执行完毕后会自动释放所锁


def func1():
    with lock:
	    time.sleep(1)
		print(f'hello func1 !')
	   

6.线程池

在线程池提交一个任务后,如果线程池种有空闲线程则分配一个线程去执行,执行完毕后再将任务交还给线程池,如果没有空闲线程则等待

pool.shutdown(True) # 等待线程池所有任务执行完毕后,主进程再继续执行–类似.join()

from concurrent.futures import ThreadPoolExecutor
import time

def func(n):
    time.sleep(1)
    print(f'hello func1 !--{n}')

pool = ThreadPoolExecutor(100) # 创建一个线程池

# 使用循环给线程池提交任务
for i in range(1, 1000):
    pool.submit(func, i)
    
pool.shutdown(True) # 等待线程池所有任务执行完毕

7.线程队列

(1)创建一个简单的线程队列–先进先出

import threading
import queue

q = queue.Queue()  # 创建一个队列(实例化)

def func1():
    q.put(1)  # 向队列存入数据
    print('向队列存入数据--1')

def func2():
	out = q.get()  # 取出队列中的数据-没有则等待
    print(f'已取出队列数据--{out}')

t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)

t1.start()
t2.start()

(2)创建进程池队列

from concurrent.futures import ThreadPoolExecutor
import time
import queue

q = queue.Queue()

def func1(n):
    time.sleep(10)
    q.put(n)
    print(f'下载完成并存入数据--{n}')

def func2(m):
    time.sleep(0.5)
    print(f'上传队列数据--{m}')

pool_1 = ThreadPoolExecutor(100)  # 创建第一个线程池
pool_2 = ThreadPoolExecutor(100)  # 创建第二个线程池

for i in range(1000):
    pool_1.submit(func1, i)

for i in range(1000):
    out = q.get()
    pool_2.submit(func2, out)

pool_1.shutdown(True)  # 等待线程池所有任务执行完毕
pool_2.shutdown(True)  # 等待线程池所有任务执行完毕

if __name__ == '__main__':
    pass

总结:

Queue.qsize() 返回队列的大小

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

Queue.full()如果队列满了,返回True,反之False,Queue.full 与 maxsize 大小对应

Queue.get([block[, timeout]])获取队列,timeout等待时间 Queue.get_nowait()

相当于Queue.get(False),非阻塞方法 Queue.put(item) 写入队列,timeout等待时间

Queue.task_done()在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号。每个get()调用得到一个任务,接下来task_done()调用告诉队列该任务已经处理完毕。

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

二、进程

在程序开发中 多线程 和 多进程 是可以结合使用,例如:创建2个进程(建议与CPU个数相同),每个进程中创建3个线程。

1.创建一个简单的多进程

进程传参和线程方法相同

需要注意的是Windows下进程启动模式默认使用的是fork模式所以执行进程必须需写在if name ==
main’:后面,关于进程模式后面有详细介绍

import multiprocessing
import time

def func1(n):
    time.sleep(1)
    print(f'hello func1 !--{n}')

def func2(n):
    time.sleep(1)
    print(f'hello func2 !--{n}')

p1 = multiprocessing.Process(target=func1, args=(1,))
p2 = multiprocessing.Process(target=func2, args=(2,))

if __name__ == '__main__':
    p1.start()
    p2.start()

2.进程锁、开始进程、阻塞进程、守护进程

(1)进程锁

import multiprocessing
import time

lock = multiprocessing.Lock()

def func1(n):
    lock.acquire()  # 上锁
    time.sleep(1)
    print(f'hello func1 !--{n}')
    lock.release()  # 解锁
    
# # 或使用with方法-
# def func1(n):
#     with lock:  
# 	    time.sleep(1)
# 	    print(f'hello func1 !--{n}')
  
    
p1 = multiprocessing.Process(target=func1, args=(1,))

if __name__ == '__main__':
    p1.start()
    p2.start()

(2)开始进程:

p1.start()  # 当前进程准备就绪,等待被CPU调度(工作单元其实是进程中的线程)。

(3)阻塞进程:

p1.join()  # 等待当前进程的任务执行完毕后再向下继续执行。

(4)守护进程:

p.daemon = 布尔值,守护进程(必须放在start之前)
p.daemon =True,设置为守护进程,主进程执行完毕后,子进程也自动关闭。
p.daemon =False,设置为非守护进程,主进程等待子进程,子进程执行完毕后,主进程才结束。

3.进程池

进程池方法同线程池方法类似,只不过进程池添加进程,要写在if name == ‘main’:后

import time
from concurrent.futures import ProcessPoolExecutor

def func(n):
    time.sleep(1)
    print(f'hello func !--{n}')


if __name__ == '__main__':
	pool = ProcessPoolExecutor(10)
	for i in range(200):
        pool.submit(func, i)
        
    pool.shutdown(True)  # 等待进程池所有任务执行完毕后再继续向下执行

4.进程队列

pass

5.进程启动模式

  • fork,【“拷贝”几乎所有资源】【支持文件对象/线程锁等传参】【unix】【任意位置开始】【快】
    父进程使用 os.fork() 来启动一个 Python 解释器进程。在这种方式下,子进程会继承父进程的所有资源,因此子进程基本等效于父进程。这种方式只在 UNIX 平台上有效,UNIX 平台默认使用这种方式来启动进程。
    官方文档:(https://docs.python.org/3/library/os.html#os.fork)
  • spawn,【run参数传必备资源】【不支持文件对象/线程锁等传参】【unix、win】【main代码块开始】【慢】
    父进程会启动一个全新的 Python 解释器进程。在这种方式下,子进程只能继承那些处理 run() 方法所必需的资源。典型的,那些不必要的文件描述器和 handle 都不会被继承。使用这种方式来启动进程,其效率比使用 fork 或 forkserver 方式要低得多。
    Windows 只支持 spawn 方式来启动进程,因此在 Windows 平台上默认使用这种方式来启动进程。
    官方文档:https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Process.run)
  • forkserver,【run参数传必备资源】【不支持文件对象/线程锁等传参】【部分unix】【main代码块开始】
    如果使用这种方式来启动进程,程序将会启动一个服务器进程。在以后的时间内,当程序再次请求启动新进程时,父进程都会连接到该服务器进程,请求由服务器进程来 fork 新进程。通过这种方式启动的进程不需要从父进程继承资源。这种方式只在 UNIX 平台上有效。

修改进程模式-写在创建进程前

multiprocessing.set_start_method("fork")  # fork、spawn、forkserver

6.进程总结

python中的多线程无法利用CPU资源,在python中大部分情况使用多进程。python中提供了非常好的多进程包multiprocessing

multiprocessing模块用来开启子进程,并在子进程中执行功能(函数),该模块与多线程模块threading的编程接口类似

multiprocessing的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件

----进程方法

p.start(): 启动进程,并调用该子进程中的p.run()

p.run(): 进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法-自定义进程类中使用

p.terminate():
强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁

p.is_alive(): 如果p仍然运行,返回True

p.join([timeout]): 主线程等待p终止(强调:
是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

----进程属性

p.daemon:
默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置

p.name: 进程的名称

p.pid: 进程的pid

p.exitcode: 进程在运行时为None、如果为–N,表示被信号N结束

p.authkey:
进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功

三、协程

1.创建一个简单的协程

from gevent import monkey
monkey.patch_all()
import gevent
import time

def func(n):
    time.sleep(1)
    print(f'hello func1 !--{n}')

task_lists = []
for i in range(1, 10+1):
    task = gevent.spawn(func, i)
    task_lists.append(task)

gevent.joinall(task_lists)

四、总结:

计算密集型:如果程序中数据计算量比较大需要利用CPU多核心优势可以使用多进程(例如累加计算)。
IO密集型:如果程序中IO操作表较多建议使用多线程(例如文件读写,网络传输)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值