之前在杭州一家互联网公司任职网络爬虫,用的还是PHP,现在主流的爬虫语言应当非Python莫属了,赶紧学起来!
进程就是操作系统中执行的一个程序,操作系统以进程为单位分配存储空间,每个进程都有自己的地址空间、数据栈以及其他用于跟踪进程执行的辅助数据,操作系统管理所有进程的执行,为它们合理的分配资源。进程可以通过
fork
或spawn
的方式来创建新的进程来执行其他的任务,不过新的进程也有自己独立的内存空间,因此必须通过进程间通信机制(IPC,Inter-Process Communication)
来实现数据共享,具体的方式包括管道、信号、套接字、共享内存区等。
一个进程还可以拥有多个并发的执行线索,简单的说就是拥有多个可以获得CPU调度的执行单元,这就是所谓的线程。由于线程在同一个进程下,它们可以共享相同的上下文,因此相对于进程而言,线程间的信息共享和通信更加容易。当然在单核CPU系统中,真正的并发是不可能的,因为在某个时刻能够获得CPU的只有唯一的一个线程,多个线程共享了CPU的执行时间。使用多线程实现并发编程为程序带来的好处是不言而喻的,最主要的体现在提升程序的性能和改善用户体验。Python既支持多进程又支持多线程,因此使用Python实现并发编程主要有3种方式
:多进程、多线程、多进程+多线程
。
1. Python多进程(Multiprocessing)
Python的OS
模块封装了常见的系统调用,比如fork()函数
,进程可以利用它在Python中轻松的创建子进程。子进程是父进程的拷贝,但是子进程拥有自己的进程识别号PID(Process Identification)
,父进程中可以通过fork()函数
的返回值得到子进程的PID,而子进程中的返回值永远都是0
。子进程可以通过getpid()函数
获取父进程的PID。一个父进程可以fork出很多子进程。
由于Windows系统没有fork()调用,因此要实现跨平台的多进程编程,可以使用
multiprocessing
模块的Process
类来创建子进程,而且该模块还提供了更高级的封装,例如批量启动进程的进程池(Pool)
、用于进程间通信的队列(Queue)
和管道(Pipe)
等。
from multiprocessing import Process
import os
def run_proc(name):
print('Run child process %s(%s)...'%(name,os.getpid()))
if __name__=='__main__':
print('Parent process %s'%os.getpid())
p = Process(target=run_proc,args=('test',))
print('Child process will start')
p.start()
p.join()
print('Child process end')`
如果要启动大量的子进程,可以采用进程池(Pool)
的方式来创建子进程:
Pool常用的方法:
方法 | 含义 |
---|---|
apply() | 同步执行(串行) |
apply_async() | 异步执行(并行) |
terminate() | 立刻关闭进程池 |
join() | 主进程等待所有子进程执行完毕。必须在close或terminate()之后使用 |
close() | 等待所有进程结束后,才关闭进程池 |
from multiprocessing import Pool
import os,time,random
def long_time_task(name):
print('Run task %s (%s)...'%(name,os.getpid()))
start = time.time()
time.sleep(random.random*3)
end = time.time()
print('Task %s runs %0.2f seconds.'%(name,(end-start)))
if __name__ == '__main__':
print('Parent process %s.'%os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task,args=(i,))
print('Waiting for all subprocess done....')
p.close()
p.join()
print('All subprocess done')
在Unix/Linux下,可以使用
fork()
调用实现多进程。要实现跨平台的多进程,可以使用
multiprocessing
模块。进程间通信是通过
Queue、Pipes
等实现的。
参考资料:https://www.liaoxuefeng.com/wiki/1016959663602400/1017628290184064,感谢廖老师网站上通俗易懂的讲解!
Python多线程
Python中使用线程有两种方式:函数或者用类来包装线程对象
函数式:调用 _thread 模块中的start_new_thread()函数来产生新线程。
Python3 通过两个标准库
_thread
和threading
提供对线程的支持。
_thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。 threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:
方法名 | 解释 |
---|---|
threading.currentThread() | 返回当前的线程变量 |
threading.enumerate() | 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程 |
threading.activeCount() | 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果 |
类式: THread类提供以下方法来处理线程:
方法 | 解释 |
---|---|
un() | 用以表示线程活动的方法 |
start() | 启动线程活动 |
join([time]) | 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生 |
isAlive() | 返回线程是否活动的 |
getName() | 返回线程名 |
setName() | 设置线程名 |
#-*-coding:gbk-*-
import threading
import time
exitFlag = 0
class myThread(threading.Thread):
def __init__(self,threadID,name,counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print('开始线程:'+self.name)
print_time(self.name,self.counter,5)
print("退出线程:"+self.name)
def print_time(threadName,delay,counter):
while counter:
if exitFlag:
threadName.exit()
time.sleep(delay)
print("%s:%s"%(threadName,time.ctime(time.time())))
counter-=1
thread1 = myThread(1,"Thread-1",1)
thread2 = myThread(2,"Thread-2",2)
if __name__ == '__main__':
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print_time('退出主线程')
参考网站:https://www.runoob.com/python3/python3-multithreading.html
多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。在这种情况下,
“锁”(Lock)
就可以派上用场了。我们可以通过“锁”来保护“临界资源”
,只有获得“锁”的线程才能访问“临界资源”,而其他没有得到“锁”的线程只能被阻塞起来,直到获得“锁”的线程释放了“锁”,其他线程才有机会获得“锁”,进而访问被保护的“临界资源”。
from time import sleep
from threading import Thread, Lock
class Account(object):
def __init__(self):
self._balance = 0
self._lock = Lock()
def deposit(self, money):
# 先获取锁才能执行后续的代码
self._lock.acquire()
try:
new_balance = self._balance + money
sleep(0.01)
self._balance = new_balance
finally:
# 在finally中执行释放锁的操作保证正常异常锁都能释放
self._lock.release()
@property
def balance(self):
return self._balance
class AddMoneyThread(Thread):
def __init__(self, account, money):
super().__init__()
self._account = account
self._money = money
def run(self):
self._account.deposit(self._money)
def main():
account = Account()
threads = []
for _ in range(100):
t = AddMoneyThread(account, 1)
threads.append(t)
t.start()
for t in threads:
t.join()
print('账户余额为: ¥%d元' % account.balance)
if __name__ == '__main__':
main()
获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用
try...finally
来确保锁一定会被释放。
新概念
协程:单线程的异步编程模型,有了协程的支持,就可以基于事件驱动编写高效的多任务程序。