进程与线程
* 对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。*
很好理解,当你启动了QQ和微信那么对于操作系统来说就是有两个进程在执行;
有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
在QQ和微信中有多少小功能在运行(打字,小程序,视频等) 那么这些便可以称之为这两个进程的线程;
由于每个进程至少要做一件事情 故 每个进程其必有一个线程。
理解时可以联想下书的目录与树的内容。
多任务实现
1.多进程模式; 每个进程对应一个线程 启动多个 进程
2.多线程模式; 每个进程对应多个线程 启动一个进程 即启动多个线程
3.多进程+多线程模式。 每个进程对应多个线程 启动多个进程
多线程
多线程就是允许一个进程内存在多个控制权,以便让多个函数同时处于激活状态,从而让多个函数的操作同时运行;多线程相当于一个并发系统。并发系统一般同时执行多个任务。如果多个任务可以同时共享资源,比如说同时写入某个变量的时候,就必须处理同步上的问题;
线程的创建用threading模块。
多线程没有想象中的那么费劲,threading.Thread() 创建线程 ;
下面看一个例子:
def sleep_3():
time.sleep(3)
def sleep_5():
time.sleep(5)
if __name__ == '__main__':
start_time = time.time()
print('start sleep 3')
sleep_3()
print('start sleep 5')
sleep_5()
end_time = time.time()
print(str(end_time - start_time) + ' s')
执行结果
多线程改造:
if __name__ == '__main__':
'''利用多线程改造'''
start_time_1 = time.time()
print('threading start sleep 3')
thread_1 = threading.Thread(target=sleep_3) # 实例化一个线程对象
thread_1.start() # 使线程执行这个函数
print('threading start sleep 5')
thread_2 = threading.Thread(target=sleep_5)
thread_2.start()
thread_1.join()
thread_2.join()
end_time_1 = time.time()
print(str(end_time_1 - start_time_1) + ' s')
执行结果:
在并发状况下,指令执行的先后顺序是根据内核决定。在同一个线程内部,执行顺序必然是有先后的执行;但是在不同线程间的指令并发时,就需要考虑多线程同步问题。对多线程程序来说,同步(synchronization)是指在一定的时间内只允许某一个线程访问某个资源;在这段时间内,不允许其他的线程操作该资源。
解决多线程同步方案
给判断是否有余票和卖票,加上互斥锁,这样就不会造成一个线程刚判断没有余票,而另外一个线程就执行卖票操作。
def booth(tid):
global i
# 默认互斥锁是 open状态
global lock
while True:
# 获取锁 并锁定该锁
lock.acquire()
if i != 0:
i = i - 1
print("窗口:", tid, ",剩余票数:", i)
time.sleep(1)
else:
print("Thread_id", tid, "No more tickets")
os._exit(0)
# 线程执行完毕 互斥锁 释放
lock.release()
time.sleep(1)
if __name__ == '__main__':
i = 19
# 创建锁
lock = threading.Lock()
for k in range(1,10):
# 这里参数传递的是一个元组 单数字元素元组定义加 逗号
new_thread = threading.Thread(target=booth, args=(k,))
new_thread.start()
注:由于GIL(全局解释锁)的问题,python多线程并不能充分利用多核处理器
多进程
对于进程概念,是指正在进行的一个过程或者说一个任务,而负责执行任务的则是CPU,进程本身是一个抽象的概念。进程的创建:用户创建出来的所有进程都是由操作系统负责的,因此无论是哪一种创建进程的方式,实际上都是调用操作系统的接口创建的,进程的切换都是由操作系统控制的。
无论哪一种创建进程的方式,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的。
进程的创建用multiprocessing模块。
'''进程的创建用multiprocessing模块'''
def run_proc(name):
# 输出传入姓名并打印当前进程号
print('Run child process %s (%s)'%(name,os.getpid()))
print('Parent process1 %s.' % os.getpid())
if __name__ == '__main__':
print('Parent process2 %s.' % os.getpid())
# 创建进程run_proc,传参
p = Process(target=run_proc, args=('test',))
print('Child process will start.')
p.start()
p.join()
print('Child process end.')
print('Parent process3 %s.' % os.getpid())
从结果来看 process1 与 process2 process3 共用同一进程 新进程的进程号为30957。
创建进程的类:Process([group [, target [, name [, args [, kwargs]]]]])
target表示调用对象。
args表示调用对象的位置参数元组。
kwargs表示调用对象的字典。
name为别名。group实质上不使用。
进程的一般方法:is_alive()、join([timeout])、run()、start()、terminate()、name()
if __name__ == '__main__':
a = multiprocessing.Process(target=run_proc,args=('sulong',))
a.start()
print('p.pid',a.pid)
print('p.name',a.name)
print('p.is_alive',a.is_alive())
# 终止当前进程
print('p.terminate', a.terminate())
下面来扯扯多进程:
def task1(msg):
print('task1:hello,%s' %(msg))
time.sleep(1)
def task2(msg):
print('task2:hello,%s' %(msg))
time.sleep(1)
def task3(msg):
print('task3:hello,%s' %(msg))
time.sleep(1)
if __name__ == '__main__':
p1 = Process(target=task1, args=('one',))
p2 = Process(target=task2, args=('two',))
p3 = Process(target=task3, args=('three',))
start = time.time()
p1.start()
p2.start()
p3.start()
print("The number of CPU is:" + str(multiprocessing.cpu_count()))
for p in multiprocessing.active_children():
print("child p.name: " + p.name + "\tp.id: " + str(p.pid))
p1.join()
p2.join()
p3.join()
end = time.time()
print('3 processes take %s seconds' % (end - start))
结果:
The number of CPU is:4
child p.name: Process-3 p.id: 31105
child p.name: Process-1 p.id: 31103
child p.name: Process-2 p.id: 31104
task1:hello,one
task2:hello,two
task3:hello,three
3 processes take 1.0111210346221924 seconds
三个进程执行花费约1s,说明程序是并发执行的。
并发与并行又该如何理解?
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
所以它们最关键的点就是:是否是『同时』;Python 中没有真正的并行,只有并发
无论你的机器有多少个CPU, 同一时间只有一个Python解析器执行
来自知乎的解答
如果要启动大量的子进程,可以用进程池的方式批量创建子进程:
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 seconde' % (name, (end - start)))
if __name__ == '__main__':
print('Parent process %s.' % os.getpid())
# 定义一个进程池 个数为5
p = Pool(5)
# 执行 8个进程
# 根据输出结果看出 在进程池中只有5个进程位置 谁先执行完毕 则下一个进程 进入该进程执行
for i in range(0, 8):
# apply_asyncl
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
# 必须对Pool先调用close()方法才能join
p.join()
print('All subprocesses done.')
执行结果:
Parent process 31270.
Waiting for all subprocesses done...
Run task 0 (31276)...
Run task 1 (31277)...
Run task 2 (31278)...
Run task 3 (31279)...
Run task 4 (31280)...
Task 3 runs 0.01 seconde
Run task 5 (31279)...
Task 0 runs 0.16 seconde
Run task 6 (31276)...
Task 6 runs 0.31 seconde
Run task 7 (31276)...
Task 2 runs 0.65 seconde
Task 4 runs 1.26 seconde
Task 5 runs 1.43 seconde
Task 1 runs 1.88 seconde
Task 7 runs 2.11 seconde
All subprocesses done.
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。
当多个进程需要访问共享资源的时候,Lock可以用来避免访问的冲突
# 多进程共享资源lock
def task4(lock,f):
with lock:
f = open(f,'w+')
f.write('hello')
time.sleep(1)
f.close()
def task5(lock,f):
lock.acquire()
try:
f = open(f,'a+')
time.sleep(1)
f.write('world!')
except Exception as e :
print(e)
finally:
f.close()
lock.release()
运行结果:
>>time cost :2.0073862075805664 seconds
>>helloworld!
因为要访问共享文件,先获得锁的进程会阻塞后面的进程,因此程序运行耗时约2s。
python通过多进程实现多并行,充分利用多处理器,弥补了语言层面不支持多并行的缺点
协程
根据维基百科给出的定义,“协程 是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序”。从技术的角度来说,“协程就是你可以暂停执行的函数”
协程可以理解为生成器
其他博主讲解的生成器大家可以看一下,在这就不做详解了。
借鉴于:https://www.cnblogs.com/tyomcat/p/5486827.html,
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000