4 开启单进程
4.1 默认情况下
4.2 传参情况下
4.3 守护线程
1 进程概念
在操作系统这门课里面,进程和线程是操作系统的概念,协程不是操作系统中的概念。
而是我们程序员层面的协程,协程是由我们程序员自己来调用的,不是由操作系统来调用的,
也就是说,在操作系统中没有协程这个概念
进程(process)和线程(thread)是操作系统的基本概念,但是它们比较抽象,不容易掌握。
可以简单理解如下:
1.计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。
2.假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,
其他车间都必须停工。
背后的含义就是,单个CPU一次只能运行一个任务。
3.进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,
其他进程处于非运行状态。
4.一个车间里,可以有很多工人。他们协同完成一个任务。
5.线程就好比车间里的工人。一个进程可以包括多个线程。
6.车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。
这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。
7.可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。
里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,
其他线程必须等它结束,才能使用这一块内存。
8.一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,
就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex),
防止多个线程同时读写某一块内存区域。
9.还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。
这好比某些内存区域,只能供给固定数目的线程使用。
10.这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。
后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),
用来保证多个线程不会互相冲突。
不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。
但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
11.操作系统的设计,因此可以归结为三点:
(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,
另一方面允许进程之间和线程之间共享资源。
注意:
进程、线程都是操作系统中的基本概念,也就是说进程和线程都是操作系统层面的东西,
专业术语表达就是进程和线程的使用都是由操作系统来调度的,而不是由我们程序员自己来操控的
进程是用来做什么的呢?
进程可以用来做任务,比如,听歌、看电影等待
什么是进程?
进程是操作系统进行资源分配的最小单位。
***
比如,厨师做饭,厨师按照菜谱来做,那么,菜谱就是程序,而做饭的过程就是进程,
在这个过程中,厨师就是线程,所以,厨师才是真正的做事的,言外之意就是线程才是真正干活的,
而进程不是,进程只是一个过程。
***
2 进程的注意事项
进程中是要有线程的,进程中如果没有线程是没有意义的,一个进程中是可以有多个线程的,
一个进程中至少要有一个线程。
进程和线程都是由操作系统来调度的,进程是操作系统的一个独立的单位。
进程和程序的区别?
1.程序就是没有生命周期的
2.进程是有生命周期的,当一个任务进行完毕后,进程就不存在了
补充“CPU的工作机制:
1.当CPU遇到I/O操作的时候,会剥夺CPU的执行权限
2.当遇到的任务需要占用大量的时间的时候,也会剥夺CPU的执行权限
注意:
CPU的工作机制是来回切换做到的
I/O密集型:input output
遇到阻塞,但是不需要占用大量的CPU资源,需要等待,比如:time.sleep(3)
eg:
import time
time.sleep(10)
print(123)
计算密集型:
没有遇到阻塞,但是需要占用大量的CPU资源,也不需要等待
eg:
for i in range(100000):
i += 1
print(i)
进程的并发和并行
1.并行:在'同一时刻',同时执行任务
CPU是有多个核的,例如:单核的,多核的
问:单核的CPU能不能做到并行?
单核的CPU是做不到并行的
注意:在同一时刻要想执行多个任务,必须要求CPU有多个处理器(核)
2.并发:在一段时间内,看起来是同时执行任务,事实上,不是同一时刻
3 同步异步阻塞非阻塞
1.同步:依赖于上一次的结果
2.异步:不依赖于上一次的结果
注意:异步的效率肯定比同步的效率高
3.阻塞:
注意:网络编程中有两种模式:阻塞和非阻塞,默认是采用阻塞方式。
3.1 什么是阻塞和非阻塞
阻塞和非阻塞是对操作请求者在等待返回结果时的状态描述,
阻塞时,在操作请求结果返回前,当前线程会被挂起,得到结果之后返回;
非阻塞时,如果不能立刻得到结果,系统不会挂起当前线程,
而是直接返回错误信息,因此对应非阻塞的情况,调用者需要定时轮询查看处理状态。
3.2 本质:阻塞和非阻塞本质上是本地系统对socket的不同处理方式,并不影响socket链接,
也不会影响通信对方,通信双方可以自由选择阻塞还是非阻塞,
例如:客户端设置成阻塞,服务器端accept 后可以设置成非阻塞都是可以的。
3.3 非阻塞是阻塞方式的改进方案,在大部分情况下可以获得更好的性能。
3.4 阻塞模式下什么情况下会阻塞
不管是TCP还是UDP,accept,connect,send,recv等操作都可以分类为发送数据和接收数据,
connect和accept本质上也是发送和接收数据。
对于接收数据,当然需要对方有发送数据,自己能接收到数据,不然就会阻塞。
对于发送数据,数据的发送是由系统管理的,应用层操作只是将数据写入内存缓冲区,因此大部分情况下是不阻塞的,
只有当缓冲区满,才会阻塞,(缓冲区不足以保存当前请求发送的数据,不确定是否会阻塞,还是只拷贝内存空闲大小数据)。
3.5 阻塞可能导致的问题
阻塞会导致线程挂起,如果是单线程处理,其它操作会被中断,得不到响应。
一直阻塞会导致程序无法使用,例如:如果对端未发送数据,接收端会一直阻塞在recv函数,导致接收端也无法发送数据了,除非使用多线程处理。
3.6 阻塞的改进方法
设置超时时间
socket支持设置选项:
SO_RCVTIMEO :接收超时时间
SO_SNDTIMEO :发送超时时间
这两个选项对send, sendmsg, recv, recvmsg, accept, connect等都有效,特别注意对accept 和connect同样有效。
这两个选项设置后,若执行超时,函数返回-1,并设置errno为EAGAIN或EWOULDBLOCK;如果是connect超时,返回-1,但errno设置为EINPROGRESS。
4.非阻塞:设置超时时间虽然可以解决阻塞导致的问题,但是需要占用一定的时长,
并且时间并不好设置,时间设置过小,容易导致操作中止,设置过长又没太大意义;
因此系统实现了非阻塞方式,配合异步编程,效率得到了很大的提升。
"""关于同步异步阻塞非阻塞的另外一个版本的理解:(推荐使用这个版本理解效果可能更好)"""
"阻塞”与"非阻塞"与"同步"与“异步"不能简单的从字面理解,提供一个从分布式系统角度的回答。
1.同步与异步同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。
但是一旦调用返回,就得到返回值了。换句话说,就是由*调用者*主动等待这个*调用*的结果。
而异步则是相反,*调用*在发出之后,这个调用就直接返回了,所以没有返回结果。
换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,
*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。
典型的异步编程模型比如Node.js举个通俗的例子:
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",
然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。
然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。
2. 阻塞与非阻塞阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
2.1阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
2.2非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
还是上面的例子,你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,
你会一直把自己“挂起”,直到得到这本书有没有的结果,
如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了,
当然你也要偶尔过几分钟check一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。
4 开启单进程
4.1 默认情况下
"""
参数默认情况下:比如:name是默认值None
group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None
"""
from multiprocessing import Process
def task():
with open('a.txt', 'w', encoding='utf-8') as wf:
wf.write('helloword')
开启一个进程来执行task这个任务
如何开启进程
"""在windows系统中,开启进程必须写在__main__中,在其它系统中不用"""
if __name__ == '__main__':
得到一个对象,还没有开启进程,知识实例化了一个进程对象
p = Process(target=task)
"""
def __init__(
self, group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None):
name:代表的是进程的名字
"""
开启进程:目的是为了完成任务
p.start()
查看进程的名字
print(p.name) # 没有传参则默认Process-1
4.2 传参情况下
"""
传递参数情况下:比如:name是传了参数的
group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None
"""
from multiprocessing import Process
def task(name, name1):
print(name)
print(name1)
with open('a.txt', 'w', encoding='utf-8') as wf:
wf.write('helloword')
开启一个进程来执行task这个任务
如何开启进程
"""在windows系统中,开启进程必须写在__main__中,在其它系统中不用"""
if __name__ == '__main__':
得到一个对象,还没有开启进程,知识实例化了一个进程对象
p = Process(target=task, name="json", args=('kevin', 'tank'))
"""
def __init__(
self, group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None):
name:代表的是进程的名字
"""
开启进程
p.start()
# 传参后的结果是
# json
# kevin
# tank
# age 18
# gender male
4.3 守护线程
1.暂时未守护
from multiprocessing import Process
# def task():
def task(name, name1, age, gender):
print(name)
print(name1)
print("age", age)
print('gender', gender)
# 123
# kevin
# tank
# age 18
# gender male
with open('a.txt', 'w', encoding='utf-8') as wf:
wf.write('helloword')
# 开启一个进程来执行task这个任务
# 如何开启进程
"""在windows系统中,开启进程必须写在__main__中,在其它系统中不用"""
if __name__ == '__main__':
# 得到一个对象,还没有开启进程,知识实例化了一个进程对象
# p = Process(target=task)
p = Process(target=task, name="json", args=('kevin', 'tank'), kwargs={'age': 18, 'gender': 'male'})
"""
def __init__(
self, group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None):
name:代表的是进程的名字
args=(),传参数的
kwargs={},
daemon:守护线程
"""
# 开启进程
p.start()
# 传参后的结果是
# json
# kevin
# tank
# age 18
# gender male
# 先输出123
'''
这个开启进程只是通知操作系统去开启进程,因为开启进程是需要时间的,
所以,在一瞬间进程并没有开启,然后代码先往下执行了,所以先打印了123
'''
print(123)
2.开启守护线程:p.daemon = True
from multiprocessing import Process
# def task():
def task(name, name1, age, gender):
print(name)
print(name1)
print("age", age)
print('gender', gender)
with open('a.txt', 'w', encoding='utf-8') as wf:
wf.write('helloword')
# 开启一个进程来执行task这个任务
# 如何开启进程
"""在windows系统中,开启进程必须写在__main__中,在其它系统中不用"""
if __name__ == '__main__':
# 得到一个对象,还没有开启进程,知识实例化了一个进程对象
# p = Process(target=task)
'''开启的这个进程是子进程'''
p = Process(target=task, name="json", args=('kevin', 'tank'), kwargs={'age': 18, 'gender': 'male'})
"""
def __init__(
self, group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None):
name:代表的是进程的名字
args=(),传参数的
kwargs={},
daemon:守护进程
守护进程的作用:
主进程结束,子进程也结束,所以最后代码只输出了123
注意:
需要注意的是守护进程必须在开启进程之前,即:
把p进程设置为守护进程
p.daemon = True
开启进程
p.start()
"""
把p进程设置为守护进程
p.daemon = True
# 开启进程
p.start()
'''
这个开启进程只是通知操作系统去开启进程,因为开启进程是需要时间的,
所以,在一瞬间进程并没有开启,然后代码先往下执行了,所以先打印了123
是因为主进程执行print(123),而task()是子进程执行的
'''
print(123)
5 Process的方法介绍
from multiprocessing import Process
def task():
print("task")
if __name__ == '__main__':
p = Process(target=task)
p.start()
'''
杀死进程
p.terminate()
查看p进程是否存活
print(p.is_alive()) # True
import time
time.sleep(1)
print(p.is_alive()) # False
'''
若现在让你先执行子进程中的代码,然后再执行主进程中的代码,怎么做?
join 先等待子进程执行完毕,再执行主进程
进程之间是相互独立的
p.join()
print(123)
6 进程锁
"""
进程锁(也称为互斥锁)是一种用于控制并发访问共享资源的机制。
它的主要目的是防止多个进程同时修改或访问共享资源导致的数据不一致或竞态条件。
在多进程环境中,多个进程可能同时访问和修改共享资源,如共享内存区域、文件等。
如果没有适当的同步机制,可能会导致数据损坏、结果不确定或其他不可预期的问题。
进程锁通过提供互斥访问的机制来解决这个问题。当一个进程获得了进程锁后,
其他进程必须等待该进程释放锁才能继续访问共享资源。这样可以确保在任意时刻只有一个
进程可以访问共享资源,从而避免了并发访问导致的问题。
进程锁的意义在于保护共享资源的完整性和一致性。
它确保了多个进程在并发访问共享资源时的顺序和互斥性,
避免了竞态条件和数据不一致的情况发生。进程锁是实现多进程并发安全的重要工具之一。
"""
def task(i, lock):
# 上锁
lock.acquire()
print('第%s个进程进来了' % i)
print('第%s个进程走了' % i)
# 释放锁
lock.release()
from multiprocessing import Process
from multiprocessing import Lock
if __name__ == '__main__':
lock = Lock()
for i in range(5):
p = Process(target=task, args=(i, lock))
p.start()
7 进程间的通信
"""
进程是可以开启多个的,进程与进程之间是相互独立的,互不影响的,
进程是操作系统的一个独立单元
当一个进程崩溃的时候,其他的进程不受影响
在一个进程中不能修改另外一个进程的数据,言外之意就是进程之间的数据是隔离的,互不影响的
进程通信是指在进程间传输数据(交换信息)。
进程通信根据交换信息量的多少和效率的高低,分为低级通信(只能传递状态和整数值)
和高级通信(提高信号通信的效率,传递大量数据,减轻程序编制的复杂度)。
其中高级进程通信分为三种方式:共享内存模式、消息传递模式、共享文件模式。
"""
from multiprocessing import Process
n = 100
def task():
global n
n = 1
if __name__ == '__main__':
p = Process(target=task)
p.start()
p.join()
print('n=', n) # n= 100