一、多任务编程
1、所谓多任务编程就是操作系统可以同时运行多个任务。实质上是每个任务交替执行,每一个任务约执行0.01秒,由于速度太快看着像是所有任务一起执行。
二、多进程编程
1、编写完毕的代码,在没有运行的时候,称之为程序
正在运行的代码,称为进程
2、Python的os模块封装了常见的系统调用,其中有fork,可以在python程序中创建子进程。
- Unix/Linux操作系统有一个fork()系统调用,普通函数调用一次返回一次,但是fork()调用一次,返回两次,操作系统会自动把当前进程(称为父进程)复制了一份(称为子进程),在父进程和子进程内返回。
- 子进程永远返回0,而父进程返回子进程的ID。
- os.getpid():获取当前进程的pid
- os.getppid():获取当前进程的父进程pid
例:
import os
money = 100
p = os.fork()
if p == 0:
money = 200
print('子进程PID为%s 子进程返回money为:%d'%(os.getpid(),money))
else:
print('创建子进程ID%s,父进程是%d'%(p,os.getppid()))
print(money)
在多进程中,每一个进程所有数据(包括全局遍历)都各有一份,互不影响
3、Windows没有fork调⽤,由于Python是跨平台的, multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了⼀个Process类来代表⼀个进程对象。
Process([group [, target [, name [, args [, kwargs]]]]])
- target:表示这个进程实例所调⽤对象;
- args:表示调⽤对象的位置参数元组; (是一个可迭代对象)
- kwargs:表示调⽤对象的关键字参数字典;
- name:为当前进程实例的别名;
- group:⼤多数情况下⽤不到;
Process类常⽤⽅法: - is_alive(): 判断进程实例是否还在执⾏;
- join([timeout]): 是否等待进程实例执⾏结束,或等待多少秒;
- start():启动进程实例(创建⼦进程);
- run():如果没有给定target参数,对这个对象调⽤start()⽅法时,就将执 ⾏对象中的run()⽅法;
- terminate():不管任务是否完成,⽴即终⽌;
Process类常⽤属性: - name:当前进程实例别名,默认Process-N,N为从1开始计数;
- pid:当前进程实例的PID值;
Process代码示例
from multiprocessing import Process
import time
def task1():
print('正在听音乐')
time.sleep(1)
def task2():
print('正在编程.....')
time.sleep(0.5)
def no_muti():
for i in range(2):
task1()
for i in range(5):
task2()
def use_multi():
processes = []
for i in range(2):
p = Process(target=task1)
p.start()
processes.append(p)
for i in range(5):
p = Process(target=task2)
p.start()
processes.append(p)
[j.join() for j in processes]
if __name__ == '__main__':
start_time = time.time()
use_multi()
# no_muti()
end_time = time.time()
print('时间为:',(end_time-start_time))
多线程实现素数:
from multiprocessing import Process
def is_prime(num):
if num == 1:
return False
for i in range(2,num):
if num % i == 0:
return False
return True
def task(num):
if is_prime(num):
print('%d是素数!'%(num))
def use_prime():
process = []
for i in range(1,1000):
p = Process(target=task,args=(i,))
p.start()
process.append(p)
[i.join() for i in process]
def no_mutli():
for num in range(1,1000):
task(num)
def use_pool():
from multiprocessing import Pool
from multiprocessing import cpu_count
p = Pool(cpu_count())
p.map(task,list(range(1,1000)))
p.close()
p.join()
if __name__ == '__main__':
import time
start_time = time.time()
# no_mutli()
use_prime()
# use_pool()
end_time = time.time()
print(end_time-start_time)
4、Pool进程池
- 当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。
- Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。
import multiprocessing
def job(id):
print("start %d...." % (id))
print("end %d...." % (id))
# 创建进程池对象
pool = multiprocessing.Pool(processes=4)
# 给进程池分配任务;
for i in range(10):
pool.apply_async(job, args=(i + 1,))
pool.close()
# 等待所有的子进程执行结束, 关闭进程池对象;
pool.join()
print("所有任务执行结束.....")
5、进程间的通信
常使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue 本身是⼀个消息列队程序。
- ueue.qsize(): 返回当前队列包含的消息数量;
- Queue.empty(): 如果队列为空,返回True,反之False ;
- Queue.full(): 如果队列满了,返回True,反之False;
- Queue.get([block[, timeout]]):获取队列中的⼀条消息,然后将其从列队中移除,block默认值为True;
- Queue.get_nowait():相当Queue.get(False);
- Queue.put(item,[block[, timeout]]):将item消息写⼊队列,block默认值 为True;
- Queue.put_nowait(item):相当Queue.put(item, False);
import time
import multiprocessing
class Producer(multiprocessing.Process):
def __init__(self,queue):
super(Producer, self).__init__()
self.queue = queue
def run(self):
for i in range(10):
self.queue.put(i)
time.sleep(0.1)
print('传达消息,内容为%s'%(i))
class Consumer(multiprocessing.Process):
def __init__(self,queue):
super(Consumer, self).__init__()
self.queue = queue
def run(self):
while True:
time.sleep(0.1)
recvData = self.queue.get()
print('接受到另一进程传递的数据:%s'%(recvData))
if __name__ == '__main__':
q = multiprocessing.Queue()
p1 = Producer(q)
c1 = Consumer(q)
p1.start()
c1.start()
p1.join()
c1.join()
三、多线程编程
1、线程是操作系统能够进行运算的最小单位。它包含在进程之中,是进程的实际运作单位。
每一个进程至少有一个线程,即进程本身。进程可以启动多个线程。操作系统像并行“进程”一样执行这些线程。
2、进程和线程的区别
- 进程是资源分配的最小单位,线程是程序执行的最小单位。
- 进程有自己的独立地址空间。线程是共享进程中的数据的,使用相同的地址空间。
- 进程之间的通信需要以通信的方式(IPC)进行。线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,难点:处理好同步与互斥。
3、两种不同的线程
- 内核线程
- 用户空间线程或用户线
- 内核线程是操作系统的一部分,而内核中没有实现用户空间线程。
4、实现多线程:python的thread模块是⽐较底层的模块,python的threading 模块是对thread做了⼀些包装的,可以更加⽅便的被使⽤。
代码实现如下:
import time
import threading
from threading import Thread
def task():
print('当前线程个数:',threading.active_count())
time.sleep(1)
print('当前线程信息:',threading.current_thread())
if __name__ == '__main__':
t1 = threading.Thread(target=task)
t2 = threading.Thread(target=task)
t1.start()
t2.start()
print(threading.active_count())
t1.join()
t2.join()
5、线程的几种状态
6、IP地址归属地批量查询任务
代码实现:
import threading,json
import requests
def get_addr(ip):
ur1 = 'http://ip-api.com/json/1.1.1.%s'%(ip)
pageContent = requests.get(ur1).text
dict_data = json.loads(pageContent)
print("""
%s
所在城市%s
国家%s
"""%(ip,dict_data["city"],dict_data["country"]))
if __name__ == '__main__':
# get_addr(3)
threads = []
for i in range(1,25):
t = threading.Thread(target=get_addr,args=(i,))
t.start()
threads.append(t)
# r = random.random()
# time.sleep(r)
[i.join() for i in threads]
7、共享全局变量
- 优点: 在⼀个进程内的所有线程共享全局变量,能够在不使⽤其他⽅式的前提 下完成多线程之间的数据共享(这点要⽐多进程要好)
- 缺点: 线程是对全局变量随意遂改可能造成多线程之间对全局变量 的混乱(即线程⾮安全)
8、解决线程不安全问题
Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
- 线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作:
同步就是协同步调,按预定的先后次序进⾏运⾏。如:你说完,我再说。
"同"字从字⾯上容易理解为⼀起动作 其实不是,
"同"字应是指协同、协助、互相配合。
代码实现:
money = 0
def add():
for i in range(1000000):
global money
lock.acquire()
money += 1
lock.release()
def reduce():
for i in range(1000000):
global money
lock.acquire()
money -= 1
lock.release()
if __name__ == '__main__':
from threading import Lock, Thread
lock = Lock()
t1 = Thread(target=add())
t2 = Thread(target=reduce())
t1.start()
t2.start()
t1.join()
t2.join()
print(money)
9、在线程共享多个资源时,如果两个线程分贝占有一部分资源并同时等待对方的资源,就会造成死锁。
四、协程
1、协程,又称微线程,纤程。英文名Coroutine。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
2、协程的优势
- 执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制,
- 没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显。
- 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率高很多。
3、_gevent实现协程
import gevent
import requests
import json
from gevent import monkey
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from threading import Thread
from gevent import monkey
# 打补丁
monkey.patch_all()
def task(ip):
"""获取指定IP的所在城市和国家并存储到数据库中"""
# 获取网址的返回内容
url = 'http://ip-api.com/json/%s' % (ip)
try:
response = requests.get(url)
except Exception as e:
print("网页获取错误:", e)
else:
# 默认返回的是字符串
"""
{"as":"AS174 Cogent Communications","city":"Beijing","country":"China","countryCode":"CN","isp":"China Unicom Shandong Province network","lat":39.9042,"lon":116.407,"org":"NanJing XinFeng Information Technologies, Inc.","query":"114.114.114.114","region":"BJ","regionName":"Beijing","status":"success","timezone":"Asia/Shanghai","zip":""}
"""
contentPage = response.text
# 将页面的json字符串转换成便于处理的字典;
data_dict = json.loads(contentPage)
# 获取对应的城市和国家
city = data_dict.get('city', 'null') # None
country = data_dict.get('country', 'null')
print(ip, city, country)
# 存储到数据库表中ips
ipObj = IP(ip=ip, city=city, country=country)
session.add(ipObj)
session.commit()
if __name__ == '__main__':
engine = create_engine("mysql+pymysql://root:westos@172.25.254.123/pymysql",
encoding='utf8',
# echo=True
)
# 创建缓存对象
Session = sessionmaker(bind=engine)
session = Session()
# 声明基类
Base = declarative_base()
class IP(Base):
__tablename__ = 'ips'
id = Column(Integer, primary_key=True, autoincrement=True)
ip = Column(String(20), nullable=False)
city = Column(String(30))
country = Column(String(30))
def __repr__(self):
return self.ip
# 创建数据表
Base.metadata.create_all(engine)
# 使用协程
gevents = [gevent.spawn(task, '1.1.1.' + str(ip + 1)) for ip in range(10)]
gevent.joinall(gevents)
print("执行结束....")
五、总结
1、 线程与进程的区别?
- 进程是资源分配的最小单位,线程是程序执行的最小单位。
- 进程有自己的独立地址空间。线程是共享进程中的数据的,使用相同的地址空间。
- 进程之间的通信需要以通信的方式(IPC)进行。线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,难点:处理好同步与互斥。
2、 进程间内存是否共享?如何实现通讯?
- 进程不会共享。管道、信号、消息队列、信号量、套接字
3、 进程间通信的方式? - 可以使用multiprocessing模块的Queue实现多进程之间的数据传递
4、多线程有几种实现方法,都是什么?
两种。一是:thread模块、二是:threading模块
5、GIL锁是怎么回事?
- 同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中。
6、python中是否线程安全?如何解决线程安全?
- 共享全局变量并不安全;通过GIL锁解决
7、 什么叫死锁?
- 在线程间共享多个资源数据的时候,如果两个线程分别占有一部门资源并且同时等待对方的资源就会造成死锁。
8、什么是协程?常用的协程模块有哪些?
- 在子程序内部中断,转而执行其他的子程序时,在适当的时候再反回来执行。
- 协程模块有:greenlet、gevent
9、协程中的join是用来做什么用的?它是如何发挥作用的?
- 阻塞;等待所有子程序执行结束后在执行主程序。