进程和线程

目录

一、多进程

1、使用 os 模块中的 fork 方式实现多进程

2、使用 multiprocessing 模块创建多线程

3、multiprocessing 模块提供了一个 Pool 类来代表进程池对象

4、进程通信

二、多线程

1、用 threading 模块创建多线程


      在爬虫开发中,进程和线程的概念是非常重要的。提高爬虫的工作效率,打造分布式爬虫,都离不开进程和线程的身影。进程可以简单的理解为一个可以独立运行的程序单位,他是线程的集合,进程就是有一个或多个线程构成的。而线程是进程中的实际运行单位,是操作系统进行运算调度的最下单位。可理解为线程是进程中的一个最小运行单位。

   简单的理解:

      单进程单线程:一个人在一个桌子上吃菜

      单进程多线程:多个人在同一个桌子上一起吃菜

      多进程单线程:多个人每个人在自己的桌子上吃菜

一、多进程

Python 实现多进程的方式主要有两种,一种方法是使用os模块中的fork方法,另一种方法是使用 multiprocessing 模块。这两种方法的区别在于前者仅适用于 Unix/Linux 操作系统,对 Window 不支持,后者则是跨平台的实现方式。

1、使用 os 模块中的 fork 方式实现多进程

   Python 的 os 模块封装了常见的系统调用,其中就有 fork 方法。fork 方法来自于 Unix/Linux 操作系统中提供的一个 fork 系统调用,这个方法非常特殊。普通的方法都是调用一次,返回一次,而 fork 方法是调用一次,返回两次,原因在于操作系统将当前进程(父进程)复制出一份进程(子进程),这两个进程几乎完全相同,于是 fork 方法分别在父进程和子进程中返回。子进程中永远返回 0,父进程中返回的是子进程的 ID。下面举例,对 Python 使用 fork 方法创建进程进行讲解。其中 os 模块中的 getppid 方法用于获取当前进程的 ID,getppid 方法用于获取父进程的 ID。代码如下:

import os
if __name__ == '__main__':
	print('current Process (%s) start ...'%(os.getpid()))
	pid = os.fork()
	if pid < 0:
		print('error in fork')
	elif pid == 0:
		print('I am child process (%s) and my parent process is (%s)',(os.getpid(),os.getppid()))
	else:
		print('I (%s) created a chlid process (%s).',(os.getpid(),pid))

运行结果如下:

2、使用 multiprocessing 模块创建多线程

   multiprocessing模块提供了一个 Process 类来描述一个进程对象。创建子进程时,只需要传入一个执行函数和函数的参数,即可完成一个 Process 实例的创建,用 start() 方法启动进程,用 join() 方法实现进程间的同步。下面通过一个例子来演示创建多进程的流程,代码如下:

import os
from multiprocessing import Process
#子进程要执行的代码
def run_proc(name):
	print('Chile process %s (%s) Running ....'%(name,os.getpid()))
if __name__ == '__main__':
	print('Parent process %s.'%os.getpid())
	for i in range(5):
		p = Process(target=run_proc,args=(str(i),))
		print('Process will start.')
		p.start()
	p.join()
	print('Process end.')

温馨提示:

IDLE不能显示子进程(调用Process的子进程)打印的信息。
解决办法1:直接把.py文件拖入cmd命令窗口,用cmd命令窗口运行Python程序
解决办法2:安装PyCharm,在PyCharm中运行
以上两种方法都不能打印进程池Pool调用apply_async方法执行的子进程的信息

    以上介绍了创建进程的两种方法,但是要启动大量的子进程,使用进程池批量创建子进程的方式更加常见,因为当被操作对象数目不大时,可以直接利用 multiprocessing 中的 Process 动态生成多个进程,如果是上百个、上千个目标,手动去限制进程数量却有太过繁琐,这时候进程池 Pool 就出现了。

3、multiprocessing 模块提供了一个 Pool 类来代表进程池对象

   Pool 可以提供指定数量的进程供用户调用,默认大小是 CPU 的核数。当有新的请求提交到 Pool 中时,如果池还没有满,那么就会创建一个新的进程来执行该请求;但是如果池中的进程数已经达到规定最大值,那么该请求就会等待,知道哦啊池中有进程结束,才会创建新的进程来处理他。下面通过一个例子来演示进程池的工作流程,代码如下:

# *-* coding:utf-8 *-*
import os
from multiprocessing import Pool
import os,time,random

def run_task(name):
	print('Task %s (pid = %s) is running ...'%(name,os.getpid()))
	time.sleep(random.random() * 3)
	print('Task %s end.'%name)

if __name__ == '__main__':
	print('Current process %s.'% os.getpid())
	p = Pool(processes = 3)
	for i in range(10):
		p.apply_async(run_task,args=(i,))
	print('Waiting for all subprocesses done....')
	p.close()
	p.join()
	print('All subprocesses done.')

运行结果如下:

C:\Windows\System32>python C:\Users\wyb\Desktop\python\爬虫test\AAA.py
Current process 9932.
Waiting for all subprocesses done....
Task 0 (pid = 2176) is running ...
Task 1 (pid = 9276) is running ...
Task 2 (pid = 7116) is running ...
Task 0 end.
Task 3 (pid = 2176) is running ...
Task 1 end.
Task 4 (pid = 9276) is running ...
Task 2 end.
Task 5 (pid = 7116) is running ...
Task 4 end.
Task 6 (pid = 9276) is running ...
Task 5 end.
Task 7 (pid = 7116) is running ...
Task 6 end.
Task 8 (pid = 9276) is running ...
Task 3 end.
Task 9 (pid = 2176) is running ...
Task 7 end.
Task 8 end.
Task 9 end.
All subprocesses done.

   上述程序先创建了容量为3的进程池,依次向进程池中添加了 10 个任务。从运行结果中可以看到虽然添加了 5 个任务,但是一开始只运行了 3 个,而且每次最多运行 3 个进程。当一个任务结束了,新的任务依次添加进来,任务执行使用的进程依然是原来的进程,这一点通过进程的 pid 就可以看出来。

温馨提示:

   Pool 对象调用 join() 方法会等待所有子进程执行完毕,调用 join() 之前必须先调用 close() ,调用 close() 之后就不能继续添加新的 Process 了。

4、进程通信

   假如创建了大量的进程,那进程间通信是必不可少的。Python 提供了多种进程间通信的方式,例如 Queue、Pipe、Value+Array 等。本章主要讲解 Queue 和 Pipe 方式。两种方式的区别在于:前者用于在多个进程间实现通信,后者常用来在两个进程间通信。

Queue 通信方式:Queue 是多进程安全的队列,可以使用 Queue 实现多进程之间的数据传递。有两个方法:Put 和 Get 可以进行 Queue 操作。

   Put 方法用以插入数据到队列中,它还有两个可选参数:blocked 和 timeout 。如果 blocked 为 True (默认值),并且 timeout 为正值,该方法会阻塞 timeout 指定的时间,直到该队列有剩余时间。如果超时,会抛出 Queue.Full 异常。如果 blocked 为 False ,但该 Queue 已满,会立即抛出 Queue.Full 异常。

   Get 方法可以从队列读取并且删除一个元素。同样,Get 方法有两个可选参数:blocked 和 timeout 。如果 blocked 为 True (默认值),并且 timeout 为正值,那么在等待时间内没有取出任何元素,会抛出 Queue.Empty 异常。如果 blocked 为 False ,分两种情况:如果 Queue 有一个值可用,则立即返回该值否则,如果队列为空,则立即抛出 Queue.Empty 异常。

   下面通过一个例子进行说明:在父进程中创建三个子进程,两个子进程往 Queue 中写入数据,一个子进程从 Queue 中读取数据。程序实例如下:

# *-* coding:utf-8 *-*
import os
from multiprocessing import Process,Queue
import os,time,random

#写数据进程执行的代码
def proc_write(q,urls):
	print('Process (%s) is writing ...' % os.getpid())
	for url in urls:
		q.put(url)
		print('Put %s to queue ...' %(url))
		time.sleep(random.random())
		
#读数据进程执行测代码:
def proc_read(q):
	print('Process (%s) is reading....' % os.getpid())
	while True:
		url = q.get(True)
		print('Get %s from queue.' % url)
		

if __name__ == '__main__':
	#父进程创建 Queue,并传给各个子进程
	q = Queue()
	proc_writer1 = Process(target=proc_write,args=(q,['url_1','url_2','url_3']))
	proc_writer2 = Process(target = proc_write,args=(q,['url_4','url_5','url_6']))
	proc_reader = Process(target = proc_read,args = (q,))
	#启动子进程 proc_writer,写入:
	proc_writer1.start()
	proc_writer2.start()
	#启动子进程 proc_reader ,读取:
	proc_reader.start()
	#等待 proc_writer 结束
	proc_writer1.join()
	proc_writer2.join()
	#proc_reader 进程里是死循环,无法等待其结束,只能强行终止
	proc_reader.terminate()

运行代码如下:

Pipe 通信方法:

   Pipe 常用来在两个进程间进行的通信,两个进程分别位于管道的两端。

   Pipe 方法返回(conn1,conn2)代表一个管道的两端。Pipe 方法有 duplex 参数,如果 deplex 参数为 True (默认值),那么这个管道是全双工模式,也就是说 conn1 和 conn2 均可收发。若 duplex 为 False,conn1 只负责接收信息,conn2 只负责发送信息。send 和 recv 方法分别是发送和接收信息的方法。例如,在全双工模式下,可以调用 conn1.send 发送消息,conn1.recv 接收消息。如果没有信息可接收,recv 方法会一直阻塞。如果管道已经被关闭,那么 recv 方法会抛出 EOFError 。

   下面通过一个例子进行说明:创建两个进程,一个子进程通过 Pipe 发送数据,一个子进程通过 Pipe 接收数据。示例如下:

# *-* coding:utf-8 *-*
import multiprocessing
import os,time,random


def proc_send(pipe,urls):
	
	for url in urls:
		print('Process (%s) send:%s' % (os.getpid(),url))
		pipe.send(url)
		time.sleep(random.random())
		

def proc_recv(pipe):
	
	while True:
		print('Process (%s) rev:%s' % (os.getpid(),pipe.recv()))
		time.sleep(random.random())
		

if __name__ == '__main__':
	pipe = multiprocessing.Pipe()
	p1 = multiprocessing.Process(target = proc_send,args = (pipe[0],['url_'+str(i) for i in range(10) ]))
	p2 = multiprocessing.Process(target = proc_recv,args = (pipe[1],))
	p1.start()
	p2.start()
	p1.join()
	p2.join()

运行结果输出:

注意:以上进程程序运行结果的打印顺序在不同的系统和硬件条件下略有不同。

二、多线程

多线程类似于同时执行多个不同程序,多线程运行有如下优点:

   1、可以把运行时间长的任务放在后台去处理。

   2、用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。

   3、程序的运行速度可能加快。

   4、在一些需要等待的任务实现上,如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源,如内存占用等。

Python 的标准库提供了两个模块:thread 和 threading,thread 是低级模块,threading 是高级模块,对 thread 进行了封装。绝大多数情况下,我们只需要使用 threading 这个高级模块。

1、用 threading 模块创建多线程

   threading 模块一般通过两种方式创建多线程:第一种方式是把一个函数传入并创建 Thread 实例,然后调用 start 方法进行执行;第二种方式是直接从 threading.Thread 继承并创建线程类,然后重写 __init__ 方法和 run 方法。

   首先介绍第一种方法,通过一个简单例子演示创建多线程的流程:

# *-* coding:utf-8 *-*
import random
import time,threading
#新线程执行的代码:
def thread_run(urls):
	print('Current %s is running...'% threading.current_thread().name)
	for url in urls:
		print('%s --->>> %s'% (threading.current_thread().name,url))
		time.sleep(random.random())
	print('%s ended.'%threading.current_thread().name)
print('%s if running...'% threading.current_thread().name)
t1 = threading.Thread(target = thread_run,name = 'Thread_1',args = (['url_1','url_2','url_3'],))
t2 = threading.Thread(target = thread_run,name = 'Thread_2',args = (['url_4','url_5','url_6'],))
t1.start()
t2.start()
t1.join()
t2.join()
print('%s ended.'%threading.current_thread().name)

运行结果:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值