Python学习笔记——Day06
时隔已久的第六天,加油加油,今天做进程和线程
进程&线程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。进程曾经是分时操作系统的基本运作单位。在早期面向进程设计的计算机结构中(如早期的UNIX,Linux2.4及更早的版本),进程是程序的基本执行实体;在当代面向线程设计的计算机结构中(当代多数操作系统,Linux2.6及更新的版本),进程本身并不是基本运行单位,而是线程的容器。程序是指令、数据及其组织形式的描述,进程才是程序真正运行实例。若干进程有可能与同一个程序相关系,且每个进程皆可以同步或异步的方式独立运行。现代计算机系统可在同一时间内以进程的形式将多个程序加载到存储器中。
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
概念提出
进程是60年代初首先由麻省理工学院的MULTICS系统和IBM公司的CTSS/360系统引入的。
进程的特征
- 动态性:进程的实质是程序在多道操作系统中的一次执行过程,进程是动态产生,动态消亡的。
- 并发性:任何进程都可以同其他进程一起并发执行
- 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
- 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成
Python中的多进程
先来看看单进程的一个例子
#!usr/bin/python
# -*- coding: utf-8 -*-
# author: 李爽
# description:单进程举例
from random import randint
from time import sleep, clock
def play_video(videoname):
print("开始播放%s" % videoname)
video_time = randint(5, 8)
sleep(video_time)
print("%s播放完毕,共%d秒" % (videoname, video_time))
def main():
start = clock()
play_video("动作片.avi")
play_video("龙珠z第271集.mp4")
end = clock()
print("共使用%f秒" % (end - start))
if __name__ == '__main__':
main()
运行结果
开始播放动作片.avi
动作片.avi播放完毕,共5秒
开始播放龙珠z第271集.mp4
龙珠z第271集.mp4播放完毕,共8秒
共使用13.000016秒
从上面的结果得知,代码是顺序执行的,就是串行的方式进行,意味着先播放动作片才能播放龙珠z第271集,也就是两个时间相加,但是呢,我就想使用分屏,左边播放动作片,而右边播放龙珠,这时候我们就需要使用多进程的方式将两个播放任务放到不同的进程。
多进程的例子
#!usr/bin/python
# -*- coding: utf-8 -*-
# author: 李爽
# description:多进程举例
from multiprocessing import Process
from random import randint
from time import sleep, clock
def play_video(videoname):
print("开始播放%s" % videoname)
video_time = randint(5, 8)
sleep(video_time)
print("%s播放完毕,共%d秒" % (videoname, video_time))
def main():
start = clock()
p1 = Process(target=play_video, args=('动作片.avi',))
p2 = Process(target=play_video, args=('龙珠z第271集.mp4',))
p1.start()
p2.start()
p1.join()
p2.join()
end = clock()
print("共使用%f秒" % (end - start))
if __name__ == '__main__':
main()
运行结果
开始播放动作片.avi
开始播放龙珠z第271集.mp4
动作片.avi播放完毕,共6秒
龙珠z第271集.mp4播放完毕,共7秒
共使用7.192672秒
看吧,两边看,多进程就快多了,能节约很多时间,我们通过Process类创建了进程对象,通过target参数传入一个参数表示进程启动后要执行的代码,后面args是参数的元组,通过start方法启动进程,join方法等进程执行结束。
Python中的多线程
单线程举例
#!usr/bin/python
# -*- coding: utf-8 -*-
# author: 李爽
# description:单线程举例
import time
def saySorry():
print('sorry')
time.sleep(1)
if __name__ == "__main__":
start = time.clock()
for i in range(5):
saySorry()
end = time.clock() - start
print("运行时间:%f" % end)
运行结果
sorry
sorry
sorry
sorry
sorry
运行时间:5.003281
无非就是每秒打印一次sorry,总共花费5秒而已。接下来看看多线程的代码,在Python早期的本版本就引入了thread模块来实现,比较老而且缺失一些内容,所以我们现在使用threading模块进行多线程编程,该模块对多线程编程提供了更好的面向对象的封装。saySorry多线程版本如下
#!usr/bin/python
# -*- coding: utf-8 -*-
# author: 李爽
# description:多线程举例
import time
import threading
def saySorry():
print('sorry')
time.sleep(1)
if __name__ == "__main__":
start = time.clock()
for i in range(5):
t = threading.Thread(target=saySorry)
# 线程启动,开始执行线程
t.start()
end = time.clock() - start
print("运行时间:%f" % end)
运行结果
sorry
sorry
sorry
sorry
sorry
运行时间:0.006523
很明显吧,大大节约了程序的运行时间,通过threading模块的Thread类创建线程,也可以通过继承Thread类创建自定义的线程类,再创建线程对象,启动线程就可以了,当start()方法调用的时候,才会真正的创建线程,并且开始执行
主线程会等待所有子线程结束才结束
#!usr/bin/python
# -*- coding: utf-8 -*-
# author: 李爽
# description:多线程举例
from threading import Thread
import time
from time import clock
class SaySorry(Thread):
def run(self):
print('sorry')
time.sleep(1)
def main():
start = clock()
s1 = SaySorry()
s2 = SaySorry()
s1.start()
s2.start()
s1.join()
s2.join()
end = clock()
print("共使用%f秒" % (end - start))
if __name__ == '__main__':
main()
多线程可以共享进程的内存空间,多线程的通信可以通过全局变量实现,但有可能会得到意想不到的结果从而导致程序失效甚至崩溃。如果一个资源被多个线程竞争使用,那么我们通常称之为“临界资源”,对“临界资源”的访问需要加上保护,否则资源会处于“混乱”的状态。下面举一个存钱的例子,账户就是一个临界自由,没有保护的情况如下
#!usr/bin/python
# -*- coding: utf-8 -*-
# author: 李爽
# description:存钱
from threading import Thread
import time
class Account(object):
def __init__(self):
self.balance = 0
def save(self, money):
# 计算存款后的结果
money_temp = self.balance + money
# 模拟存款操作业务耗时0.001秒
time.sleep(0.001)
# 修改账户余额
self.balance = money_temp
class User(Thread):
def __init__(self, account, money):
super().__init__()
self.account = account
self.money = money
def run(self):
self.account.save(self.money)
def main():
account = Account()
threads = []
# 创建200个人每人向账户中存款1元
for i in range(200):
user = User(account, 1)
threads.append(user)
user.start()
# 等待所有存款的线程都执行完毕
for user in threads:
user.join()
print("账户余额共有:%d" % account.balance)
if __name__ == '__main__':
main()
这个运行结果我就不展示了,自己看可能感觉更诧异,这太不应该了吧,哈哈,因为这里有很多线程同时开始,都是从账户为0的时候执行+1操作,所以得到的是错误的结果,那么我们怎么避免这种现象呢?加锁操作应运而生,可以通过锁来保护我们的账户,当一个线程访问到账户时,账户就被锁住了,只有它才能访问这个对象,而其他没有得到锁的线程被阻塞,只能等待,直到锁的线程放了锁,其他线程才有机会竞争锁,这样就能实现一个一个线程单独的访问账户,达到保护的目的,从而得到正确的结果。
#!usr/bin/python
# -*- coding: utf-8 -*-
# author: 李爽
# description:存钱 加锁
from threading import Thread, Lock
import time
class Account(object):
def __init__(self):
self.balance = 0
self._lock = Lock()
def save(self, money):
# 先获取锁才能执行后续的代码
self._lock.acquire()
try:
# 计算存款后的结果
money_temp = self.balance + money
# 模拟存款操作业务耗时0.001秒
time.sleep(0.001)
# 修改账户余额
self.balance = money_temp
finally:
# 在finally中执行释放锁的操作保证正常异常锁都能释放
self._lock.release()
class User(Thread):
def __init__(self, account, money):
super().__init__()
self.account = account
self.money = money
def run(self):
self.account.save(self.money)
def main():
account = Account()
threads = []
# 创建200个人每人向账户中存款1元
for i in range(200):
user = User(account, 1)
threads.append(user)
user.start()
# 等待所有存款的线程都执行完毕
for user in threads:
user.join()
print("账户余额共有:%d" % account.balance)
if __name__ == '__main__':
main()
大体上就是这样了
结语
时隔上次的博客实在是太久了,该打,以后尽量做到每日练习,每日更新。多线程多进程,重点重点重点!多多扩展。好困呀,睡觉睡觉。
如果你发现我的文章哪里有错误或者有什么好的想法可以联系我,我们一起学习共同进步,我的邮箱地址是lishuangCHN@gmail.com
let’s do more of those!