1.操作系统

操作系统为程序员操作硬件提供了接口。程序员不需要直接写程序操作硬件,只需要按照一定的规范,把数据提交给操作系统,操作系统回去操作硬盘,CPU和内存

1.1第一代计算机

"特点"
    没有操作系统的概念,所有程序都是直接操控硬件
"工作过程"
   程序员预约一段时间,在这段时间一个程序员独享计算机,其他人等待
"优点"
    程序员在申请的时间内独享资源,可以及时调整自己的程序
"缺点"
    浪费资源,一段时间内只有一个人使用

1.2第二代计算机:批处理系统

"工作过程"
    多个程序员的代码放在一起进行提交,然后机器顺序计算,得到多个程序员代码的输出
"优点"
    批处理代码,不再是一个程序员独享计算机,节省了时间
"缺点"
    1.整个流程需要人的参与,把多个程序员的代码在机器之间搬动,进行执行和得到输出
    2.计算机仍然是顺序执行的
    3.程序员不能独享计算机,不能即使调试得到结果,需要等到大家都提交完成,一起处理后得到结果,有问题不能立刻处理

1.3第三代计算机:多道程序设计

"解决第二代计算机中需要人参与的问题"
    使用SPOOLING技术,就不需要人在中间搬动磁带了
"解决第二代计算机顺序执行的问题"
多道技术
    多道技术中的多道指的是多个程序,多道技术的实现是为了解决多个程序竞争或共享一个CPU的有序调度问题,解决方式是多路复用,分为时间上的复用和空间上的复用
	"空间上的复用:"将内存分为几部分,每部分放入一个程序,这样,同一时间内存中就有了多个程序
	"空间上复用的问题:"程序之间的内存必须是分割开的,这种分割需要在硬件层面操作,由操作系统控制。如果内存彼此不分割,则一个程序可以访问另外一个程序的内存。
	首先丧失的是安全性,如qq程序可以访问操作系统的内存,这就拿到了操作系统的权限。
	其次丧失的是稳定性,如果qq程序退出,可能会导致操作系统的程序退出。
	"时间上的复用:"当一个程序在等待I/O时,或一个程序运行了一段时间,CPU就切换,运行其他程序。
"解决单个程序员不能独享计算机的问题"
	"分时操作系统:"多个终端+多道技术,可以多个程序员连接计算机,操作系统采用多道的技术,处理多个程序员的任务。所有的用户以为自己独享了计算机资源。
	第三代计算机广泛采用了必须的保护硬件(程序之间的内存彼此隔离后),分时系统才开始流行。

1.4第四代计算机:个人计算机

1.5总结

一:操作系统的作用
    1.隐藏复杂的硬件接口,提供良好的抽象接口。
    2.管理、调度进程,并且将多个进程多硬件的竞争变得有序。
二:多道技术
    1.产生背景:针对单核,实现并发
      现在的主机一般是多核,多核都会使用多道技术
	  有4个CPU,运行在cpu1上的某个程序遇到io阻塞,会等到io结束再重新调度,对被调度到4个cpu中的任意一个,具体由操作系统的调度算法决定。
	2.空间上的复用:内存中同时存在多个程序,需要硬件支持,将多个程序的内存空间分隔开来。
	3.时间上的复用:复用一个cpu时间片。
	   如果程序在进行io处理,或一个程序占用cpu较长时间,就会吧进程的状态保存下来,去处理别的程序。下次CPU切换回来,继续上次的位置,继续运行。

2.进程理论

2.1什么是进程

进程是正在运行的代码程序。负责运行程序,执行任务的是CPU。

2.2进程与程序的区别

程序仅仅是一堆代码
进程是程序的运行过程,运行起来的代码会在操作系统中产生一个进程
同一个程序运行两次,是两个进程。

2.3并发与并行

无论是并发还是并行,在用户看来都是'同时'运行的
不管是进程还是线程,都只是一个任务而已,真正做事的是CPU,但同一个CPU在同一时刻只能执行一个任务
"并发":是伪并行,即看起来是同时运行。单个cpu+多道技术可以实现并发
例如:你可能正在学习,但突然有人找你聊天,你可能聊一会,看一会视频,等对方有回复了,你在回复。你就做到了做两件事,看似同时进行,但实际上你是一会学习,一会回复,在同一时间实际只做了一件事
"并行":同时运行,需要具备多个CPU才能实现并行。
单核下,可以使用多道技术,多核下,每个CPU也可以使用多道技术。
例如:有四个CPU,6个任务,在同一时间有四个任务在执行,分别分配给了cpu0,cpu1,cpu2,cpu3
如果cpu0上的任务遇到I/O,cpu0就会处理任务5,这就是单核下的多道技术
如果cpu0上的I/O结束了,那么操作系统会重新进行分配,决定调度给那个CPU是操作系统算法和任务优先级决定的。

2.4进程的创建

"进程创建的四种形式"
1.启动操作系统,会创建一些进程。linux中ps -aux查看进程,windows中tasklist查看
2.一个进程在运行过程中开启子进程。(os.fork,subprocess.Popen都可以开启子进程)
3.windows上用户双击某个软件,Linux中启动某个服务,都可以创建一个进程。
4.批处理作业的初始化(只在大型机的批处理系统中应用。)
"linux与Windows中的进程的创建"
1.Windows中调用CreateProcess来创建进程。
2.Unix中调用fork,fork会创建一个与父进程一模一样的副本
"两者的相同与不同"
相同的是:进程创建后,父进程和子进程有个字不同的地址空间(多道技术要求进程之间内存是相互隔离的),任何一个进程中的数据要和其他进程的数据相互隔离,互不影响。
不同的是:在Unix中,子进程的初始地址空间是父进程的一个副本,子进程中的数据来源于父进程,与父进程中的数据是完全相同的,但windows中子进程与父进程并不是完全相同。

2.5进程的终止

1.程序正常退出
2.报错退出
3.被kill掉

2.6进程的层次结构

无论UNIX还是Windows,进程只有一个父进程,不同的是
1.Unix中所有的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号,该信号会被发送给与键盘相关的进程组中的所有成员。
2.在windows中,没有进程层次的概念,所有的进程地位相同,唯一类似于进程层次的,是在创建进程时,父进程会得到一个特别的令牌(称为句柄),该句柄可以用于控制子进程,但是父进程有权把该句柄传给其他子进程,这样就没有层次了。

2.7进程的状态

运行中的进程被调度到就绪态的原因
1.进程自身遇到I/O阻塞,便要让出CPU让其他进程去执行,这样就保证了CPU一直爱工作
2.与进程无关,操作系统原因,如果一个进程占用时间过多,或者优先级原因,而调用其他进程去使用CPU

2.8进程并发的实现

进程并发的实现在于,硬件中断一个正在运行的进程,会把该进程的状态信息进行保存,为此,操作系统维护一个表格,即进程表,每个进程占用一个进程表项
该表存放了进程状态的重要信息,这样当CPU再次调用时,能从该进程的上次终端位置继续执行,就好像没有中断过一样。

3.开启进程的两种方式

3.1multiprocessing模块介绍

python中的"多线程"是无法利用多核优势的
python中的"多进程"是可以充分的利用多核CPU的资源,可以使用os.cpu_count()来查看cpu数
python中使用multiprocessing模块来开启多个子进程,使用threading来开启多个线程。
multiprocessing模块功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process,Queue,Pipe,Lock等组件。
"进程与线程不同,进程没有任何共享状态,进程修改的数据,改动的仅限于该进程内。"

3.2process类的介绍

Process([group [, target [, name [, args [, kwargs]]]]])
由该类实例化得到的对象,可用来开启一个子进程
"注意"
1.需要使用关键字的方式来制定参数
2.args指定的是传给target函数的位置参数,是一个元组形式,设置方式args=("a",2)
3.kwargs指定的是传给target函数的位置参数,是一个字典的形式,设置方式kwargs={a:2,b:"test"}
"参数介绍"
group:未使用,为None
target:表示调用对象,即子进程要执行的任务,指向一个函数
args:表示target函数的位置参数,元组形式,args=(1,2,'vita',)
kwargs:表示target函数的位置参数,字典形式,kwargs={'name':'vita','age':18}
name:表示子进程的名称
"方法介绍"
p.start():启动进程,并调用该子进程中的run()方法
p.run():进程启动时运行的方法,正是该方法去调用target指定的函数,自定义的继承自Process的类,需要实现该方法
p.terminate():强制终止进程p,不会做任何的清理工作,
    如果p创建了子进程,该子进程就成了僵尸进程
    如果p还保存了一个锁,那么锁也不会被释放,进而导致死锁
p.is_alive():如果p在运行,就返回True
p.join([timeout]):主线程等待p终止
    "注意"是主线程处于等待状态,而p处于运行状态
"属性介绍"
p.daemon:默认为False,
如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止
设置为True后,p不能创建自己的新进程,必须在p.start之后设置
p.name:进程的名称
p.pid:进程的pid

3.3Process创建子进程

注意:在windows中Process()必须放到# if __name__ == '__main__':下
"方式一"
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: vita
from multiprocessing import Process
def target_func(name):
   print("子进程:",name)
if __name__ == '__main__':
# 实例化两个对象,开启两个子进程
    p1=Process(name="p1",target=target_func,args=("p1",))# 这里一定要加',',否则TypeError: target_func() takes 1 positional argument but 2 were given
    p2=Process(name="p2",target=target_func,args=("p2",))
    p1.start()
    p2.start()
    print("主进程") #会发现创建了子进程,主进程依旧在继续运行,所以先输出了主进程的内容,然后运行子进程,
		
D:\software2\Python3\install\python.exe E:/PythonProject/new-python/python-test/BasicGrammer/test.py
主进程
子进程: p1
子进程: p2

Process finished with exit code 0

"方式二"
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: vita
from multiprocessing import Process
# 这里要继承Process
class Func(Process):
   # 重用父类中的init方法,并加入新参数
   def __init__(self,name):
      super().__init__()
      self.nm=name

   # 一定要写run 方法,start方法会自动调用run方法
   def run(self): #
      print("子进程",self.nm)
if __name__ == '__main__':
# 实例化两个对象,开启两个子进程
    func = Func("p1")
    func.start()
    func1 = Func("p2")
    func1.start()
    print("主进程")
		
D:\software2\Python3\install\python.exe E:/PythonProject/new-python/python-test/BasicGrammer/test.py
主进程
子进程 p1
子进程 p2

Process finished with exit code 0

3.4pid和ppid

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: vita
from multiprocessing import Process
import os
# 这里要继承Process
class Func(Process):
   
   def __init__(self,name):
      super().__init__()
      self.nm=name

   
   def run(self): #
      # 子进程的ppid就是main的pid
      print("子进程:%s,pid:%s,ppid:%s"%(self.nm,os.getpid(),os.getppid()))
if __name__ == '__main__':
    func = Func("p1")
    func.start()
    func1 = Func("p2")
    func1.start()
    # 主进程的ppid就是你运行py文件的程序的进程号,
    # 如果使用pycharm运行,就是pycharm的进程号
    # 如果使用cmd终端运行,就是终端的进程号
    print("主进程:,pid:%s,ppid:%s"%(os.getpid(),os.getppid()))
		
D:\software2\Python3\install\python.exe E:/PythonProject/new-python/python-test/BasicGrammer/test.py
主进程:,pid:6100,ppid:11200
子进程:p1,pid:8252,ppid:6100
子进程:p2,pid:10784,ppid:6100

Process finished with exit code 0

4.Process方法和属性详解

4.1join()

在主进程运行过程中,可以开启子进程,主进程与子进程的任务分为
1.在主进程的任务与子进程的任务彼此独立时,主进程先执行完成,还要等待子进程执行完成后才统一回收资源
2.如果主进程的任务需要在执行到某个阶段,需要等待子进程执行完成后继续执行,就可使用join()
"join"
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: vita
from multiprocessing import Process
import os
import time
# 这里要继承Process
class Func(Process):

   def __init__(self,name):
       super().__init__()
       self.nm=name


   def run(self): #

       print("子进程:%s is running"%(self.nm))
       time.sleep(3)
       print("子进程:%s is finishing" % (self.nm))
if __name__ == '__main__':

   p_list=[]
   p1 = Func("p1")
   # 只是发送信号给操作系统,治愈操作系统何时执行,还要看系统内部的调用
   p1.start()
   p_list.append(p1)
   p2 = Func("p2")
   p2.start()
   p_list.append(p2)
   # 方法哦一个列表中,for循环来执行join
   for p in p_list:
       p.join()
   print("主进程运行完成")

D:\software2\Python3\install\python.exe E:/PythonProject/new-python/python-test/BasicGrammer/test.py
子进程:p1 is running
子进程:p2 is running
子进程:p1 is finishing
子进程:p2 is finishing
主进程运行完成

Process finished with exit code 0

进程执行了start()后,信号就已经发送给了操作系统,操作系统会根据自己的调度规则,进行运行
join()是让主进程等待多个子进程的运行,此时所有的子进程还是并发执行的,
所有的子进程的运行时间是运行时间最长的子进程的运行时间。当所有子进程运行完成后,继续执行主进程的代码

4.2其他属性和方法

"p.terminate(),p.is_alive,p.name,p.pid"
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: vita
from multiprocessing import Process
import os
import time
# 这里要继承Process
class Func(Process):

   def __init__(self,name):
       super().__init__()
       self.nm=name


   def run(self): #

       print("子进程:%s is running"%(self.nm))
       time.sleep(3)
       print("子进程:%s is finishing" % (self.nm))
if __name__ == '__main__':


   p1 = Func("p1")
   # 只是发送信号给操作系统,至于操作系统何时执行,还要看系统内部的调用
   p1.start()
   # 执行了terminate(),就没有执行子进程的代码了
   p1.terminate()
   # 执行terminate()也是发送给操作系统一个进程终止信号,但不会立刻终止
   print(p1.is_alive())
   print("主进程")
   print("子进程名:%s,子进程pid:%s" %(p1.name,p1.pid))
   time.sleep(2)
   # 休息2秒后,才终止
   print(p1.is_alive())

D:\software2\Python3\install\python.exe E:/PythonProject/new-python/python-test/BasicGrammer/test.py
True
主进程
子进程名:Func-1,子进程pid:14768
False

Process finished with exit code 0

5.练习

5.1思考开启进程的方式一和方式二各开启了几个进程?

总共有主进程+开启的子进程

5.2进程之间的内存空间是共享的还是隔离的?下述代码的执行结果是什么?

"进程间的数据是相互隔离的,所以主进程仍然输出100,子进程是0"
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: vita
from multiprocessing import Process



def work():
    global n
    n=0
    print('子进程内: ',n)


if __name__ == '__main__':
    n = 100  # 在windows系统中应该把全局变量定义在if __name__ == '__main__'之上就可以了
    p=Process(target=work)
    p.start()
    p.join()
    print('主进程内: ',n)

D:\software2\Python3\install\python.exe E:/PythonProject/new-python/python-test/BasicGrammer/test.py
子进程内:  0
主进程内:  100

Process finished with exit code 0

5.3基于多进程实现并发的套接字通信?

"server"
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: vita
from socket import *
from multiprocessing import Process

def talk(conn):
    while True:
        try:
            data = conn.recv(1024)
            if not data: break
            conn.send(data.upper())
        except ConnectionResetError:
            break

    conn.close()


def server(ip,port):
    server = socket(AF_INET, SOCK_STREAM)
    server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    server.bind((ip,port))
    server.listen(5)

    while True:
        conn, addr = server.accept()
        p = Process(target=talk, args=(conn,))
        p.start()

    server.close()


if __name__ == '__main__':
    server('127.0.0.1', 8080)
"client"
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: vita
from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))

while True:
    msg=input('>>: ').strip()
    if not msg:continue

    client.send(msg.encode('utf-8'))
    data=client.recv(1024)
    print(data.decode('utf-8'))

5.4思考每来一个客户端,服务端就开启一个新的进程来服务它,这种实现方式有无问题?

如果客户端过多,服务端就会开启很多个子进程,会耗尽资源

5.5改写下列程序,分别别实现下述打印效果

1、改写下列程序,分别别实现下述打印效果

from multiprocessing import Process
import time
import random

def task(n):
    time.sleep(random.randint(1,3))
    print('-------->%s' %n)

if __name__ == '__main__':
    p1=Process(target=task,args=(1,))
    p2=Process(target=task,args=(2,))
    p3=Process(target=task,args=(3,))

    p1.start()
    p2.start()
    p3.start()

    print('-------->4')
效果一:保证最先输出-------->4
"上面的代码不做更改,就是首先输出4"
-------->4
-------->1
-------->3
-------->2
效果二:保证最后输出-------->4
"p1.join()
p2.join()
p3.join()
这样就能让主进程等待子进程执行完成后,继续执行主进程,就可最后输出4"
-------->2
-------->3
-------->1
-------->4
效果三:保证按顺序输出
"   p1.start()
		p1.join()
    p2.start()
		p2.join()
    p3.start()
		p3.join()"
-------->1
-------->2
-------->3
-------->4
2、判断上述三种效果,哪种属于并发,哪种属于串行?
"前两种是并发执行,最后一个是串行"