day 38、39 进程
1 操作系统发展史
参考博客即可:
2 多道技术:单核实现并发的效果
2.1 必备知识点
顾名思义,进程即正在执行的一个过程。进程是对正在运行程序的一个抽象。
进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一。操作系统的其他所有内容都是围绕进程的概念展开的。
所以想要真正了解进程,必须事先了解操作系统
 即使可以利用的cpu只有一个(早期的计算机确实如此),也能保证支持(伪)并发的能力。将一个单独的cpu变成多个虚拟的cpu(多道技术:时间多路复用和空间多路复用+硬件上支持隔离),没有进程的抽象,现代计算机将不复存在。
- 
并发 看起来像同时运行的就可以称之为并发 
- 
并行 真正意义上的同时执行 
ps:
- 并行肯定算并发
- 单核的计算机肯定不能实现并行,但是可以实现并发!!!
补充:我们直接假设单核就是一个核,干活的就一个人,不要考虑cpu里面的内核数
#一 操作系统的作用:
    1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
    2:管理、调度进程,并且将多个进程对硬件的竞争变得有序
#二 多道技术:
    1.产生背景:针对单核,实现并发
    ps:
    现在的主机一般是多核,那么每个核都会利用多道技术
    有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个
    cpu中的任意一个,具体由操作系统调度算法决定。
    
    2.空间上的复用:如内存中同时有多道程序
    3.时间上的复用:复用一个cpu的时间片
       强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样
            才能保证下次切换回来时,能基于上次切走的位置继续运行
2.2 多道技术图解
节省多个程序运行的总耗时
2.3 多道技术重点知识
空间上的复用与时间上的复用
- 
空间上的复用:多个程序公用一套计算机硬件 
- 
时间上的复用 
例子: 洗衣服30s,做饭50s,烧水30s
单道需要110s,多道只需要任务做长的那一个 ,这里切换节省时间
例子:边吃饭边玩游戏
切换+保存状态
切换(CPU)分为两种情况
	1.当一个程序遇到IO操作的时候,操作系统会剥夺该程序的CPU执行权限
		作用:提高了CPU的利用率 并且也不影响程序的执行效率
	
	2.当一个程序长时间占用CPU的时候,操作吸引也会剥夺该程序的CPU执行权限
		弊端:降低了程序的执行效率(原本时间+切换时间)
"""
单核实现并发的效果
并发:看起来像同时运行的就可以叫做并发
并行:真正意义上的同时运行
空间与时间上的复用
	空间上
		多个程序公用一套计算机硬件
	时间上
		切换+保存状态
"""
# 切换分为两种
# 1.当一个程序遇到IO操作,操作系统会立刻剥夺该程序的cpu执行权限(提供了cpu利用率并且不影响程序的执行效率)
# 2.当一个程序长时间占用cpu,操作系统也会立刻剥夺该程序的cpu执行权限(降低了程序的运行效率但是玩出了并发的效果)
ps:人工智能相关参考网站
https://www.xfyun.cn/?ch=bd05&b_scene_zt=1
http://ai.baidu.com/creation/main/demo
作为一名python程序员当你遇到一个功能的时候,第一时间你可以考虑是否有对应的模块已经帮你实现了该功能
3 进程理论
3.1 进程概念
 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
 狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
 广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。
第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。[3] 
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
从理论角度看,是对正在运行的程序过程的抽象;
从实现角度看,是一种数据结构,目的在于清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成。
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
必备知识点:程序与进程的区别
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
程序可以作为一种软件资料长期存在,而进程是有一定生命期的。
程序是永久的,进程是暂时的。
注意:同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱。
程序就是一堆躺在硬盘上的代码,是“死”的
进程则表示程序正在执行的过程,是“活”的
3.2 进程调度
 要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随即进行的,而是需要遵循一定的法则,由此就有了进程的调度算法。
- 
先来先服务调度算法 """对长作业有利,对短作业无益"""
- 
短作业优先调度算法 """对短作业有利,多长作业无益"""
- 
时间片轮转法+多级反馈队列 
3.3 进程运行的三状态图

就绪态、运行态、阻塞态
(1)就绪(Ready)状态
当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
(2)执行/运行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。
(3)阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
3.4 两对重要概念
同步和异步
"""描述的是任务的提交方式"""
同步:任务提交之后,原地等待任务的返回结果,等待的过程中不做任何事(干等)
  	程序层面上表现出来的感觉就是卡住了
异步:任务提交之后,不原地等待任务的返回结果,直接去做其他事情
  	我提交的任务结果如何获取?
    任务的返回结果会有一个异步回调机制自动处理
阻塞非阻塞
"""描述的程序的运行状态"""
阻塞:阻塞态
非阻塞:就绪态、运行态
理想状态:我们应该让我们的写的代码永远处于就绪态和运行态之间切换
上述概念的组合:最高效的一种组合就是异步非阻塞
4 python中开启进程的两种方式
定心丸: 代码开启进程和线程的方式,代码书写基本是一样的,你学会了如何开启进程就学会了如何开启线程
之前我们已经了解了很多进程相关的理论知识,了解进程是什么应该不再困难了,刚刚我们已经了解了,运行中的程序就是一个进程。所有的进程都是通过它的父进程来创建的。因此,运行起来的python程序也是一个进程,那么我们也可以在程序中再创建进程。多个进程可以实现并发效果,也就是说,当我们的程序中存在多个进程的时候,在某些时候,就会让程序的执行速度变快。以我们之前所学的知识,并不能实现创建进程这个功能,所以我们就需要借助python中强大的模块。
4.1 multiprocess模块
仔细说来,multiprocess不是一个模块而是python中一个操作、管理进程的包。 之所以叫multi是取自multiple的多功能的意思,在这个包中几乎包含了和进程有关的所有子模块。由于提供的子模块非常多,为了方便大家归类记忆,我将这部分大致分为四个部分:创建进程部分,进程同步部分,进程池部分,进程之间数据共享。
process模块介绍
process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
参数介绍:
1 group参数未使用,值始终为None
2 target表示调用对象,即子进程要执行的任务
3 args表示调用对象的位置参数元组,args=(1,2,'jason',)
4 kwargs表示调用对象的字典,kwargs={'name':'jason','age':18}
5 name为子进程的名称
方法介绍
1 p.start():启动进程,并调用该子进程中的p.run() 
2 p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法  
3 p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
4 p.is_alive():如果p仍然运行,返回True
5 p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程  
属性介绍
1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
2 p.name:进程的名称
3 p.pid:进程的pid
4 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
5 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
在windows中使用process模块的注意事项
在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候  ,就不会递归运行了。
使用process模块创建进程
在一个python进程中开启子进程,start方法和并发效果。
# 第一种方式
from multiprocessing import Process
import time
def task(name):
    print('%s is running'%name)
    time.sleep(3)
    print('%s is over'%name)
if __name__ == '__main__':
    # 1 创建一个对象
    p = Process(target=task, args=('jason',))   # 容器类型哪怕里面只有1个元素 建议要用逗号隔开
    
    # 2 开启进程
    p.start()  # 告诉操作系统帮你创建一个进程  异步
    print('主')
# 第二种方式 类的继承
from multiprocessing import Process
import time
class MyProcess(Process):
    def run(self):
        print('hello bf girl')
        time.sleep(1)
        print('get out!')
if __name__ == '__main__':
    p = MyProcess()
    p.start()
    print('主')
总结
"""
创建进程就是在内存中申请一块内存空间将需要运行的代码丢进去
一个进程对应在内存中就是一块独立的内存空间
多个进程对应在内存中就是多块独立的内存空间
进程与进程之间数据默认情况下是无法直接交互,如果想交互可以借助于第三方工具、模块
"""
5 join方法
join是让主进程等待子进程代码运行结束之后,再继续运行。不影响其他子进程的执行
点击查看代码
from multiprocessing import Process
import time
def task(name, n):
    print('%s is running'%name)
    time.sleep(n)
    print('%s is over'%name)
if __name__ == '__main__':
     p1 = Process(target=task, args=('jason', 1))
     p2 = Process(target=task, args=('egon', 2))
     p3 = Process(target=task, args=('tank', 3))
        
     start_time = time.time()
     p1.start()
     p2.start()
     p3.start() 						 # 仅仅是告诉操作系统要创建进程
     time.sleep(50000000000000000000)      # 方式一
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   						 
     p1.join()							 # 主进程等待子进程p运行结束之后再继续往后执行
     p2.join()							 # 方式二
     p3.join()
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    start_time = time.time()
    p_list = [] 
    
    for i in range(1, 4):                # 方式三
        p = Process(target=task, args=('子进程%s'%i, i))
        p.start()
        p_list.append(p)				
        
    for p in p_list:
        p.join()
        
    print('主', time.time() - start_time)
    # 这里要理解用for的意义,把主进程p、子进程p1看做内存地址,列表中每个值都不同,代表不同的子进程
6 进程之间数据相互隔离
from multiprocessing import Process
money = 100
def task():
    global money  # 局部修改全局
    money = 666
    print('子',money)
if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    p.join()
    print(money)
7 进程对象及其他方法
7.1 PID
"""
一台计算机上面运行着很多进程,那么计算机是如何区分并管理这些进程服务端的呢?
计算机会给每一个运行的进程分配一个PID号 
如何查看
	windows电脑 
		进入cmd输入tasklist即可查看
		tasklist |findstr PID查看具体的进程
	mac电脑 
		进入终端之后输入ps aux
		ps aux|grep PID查看具体的进程 
"""
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from multiprocessing import Process, current_process
current_process().pid  # 查看当前进程的进程号
import os
os.getpid()  		   # 查看当前进程进程号
os.getppid() 	 	   # 查看当前进程的父进程进程号
p.terminate()  		   # 杀死当前进程
					   # 是告诉操作系统帮你去杀死当前进程 但是需要一定的时间 而代码的运行速度极快
time.sleep(0.1)
print(p.is_alive())    # 判断当前进程是否存活
7.2 僵尸进程与孤儿进程(了解)
# 僵尸进程
死了但是没有死透
当你开设了子进程之后 该进程死后不会立刻释放占用的进程号
因为我要让父进程能够查看到它开设的子进程的一些基本信息:占用的pid号、运行时间...
----------------------------------------------------
# 孤儿进程
子进程存活,父进程意外死亡
操作系统会开设一个“儿童福利院”专门管理孤儿进程回收相关资源
7.3 守护进程
会随着主进程的结束而结束。
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
from multiprocessing import Process
import time
def task(name):
    print('%s总管正在活着'% name)
    time.sleep(3)
    print('%s总管正在死亡' % name)
if __name__ == '__main__':
    p = Process(target=task,args=('egon',))
    # p = Process(target=task,kwargs={'name':'egon'})
    
    p.daemon = True  # 将进程p设置成守护进程,并且这一句一定要放在start方法上面才有效否则会直接报错
    p.start()
    print('皇帝jason寿终正寝')
8 进程同步(互斥锁)
 通过刚刚的学习,我们千方百计实现了程序的异步,让多个任务可以同时在几个进程中并发处理,他们之间的运行没有顺序,一旦开启也不受我们控制。尽管并发编程让我们能更加充分的利用IO资源,但是也给我们带来了新的问题。
当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题。
多个进程操作同一份数据的时候,会出现数据错乱的问题
针对上述问题,解决方式就是加锁处理:将并发变成串行,牺牲效率但是保证了数据的安全
抢票程序
from multiprocessing import Process, Lock
import json
import time
import random
# 查票
def search(i):
        with open('data','r',encoding='utf8') as f:      # 文件操作读取票数
                dic = json.load(f)
        print('用户%s查询余票:%s'%(i, dic.get('ticket_num')))
        # 字典取值不要用[]的形式 推荐使用get,写代码原则是你写的代码打死都不能报错!!!
# 买票  1.先查 2.再买
def buy(i):
        with open('data','r',encoding='utf8') as f:   	# 先查票
            dic = json.load(f)
        time.sleep(random.randint(1,3))	# 模拟网络延迟
        if dic.get('ticket_num') > 0:	# 判断当前是否有票
                dic['ticket_num'] -= 1	# 修改数据库 买票
                with open('data','w',encoding='utf8') as f: # 写入数据库
                        json.dump(dic,f)
                print('用户%s买票成功'%i)
        else:
                print('用户%s买票失败'%i)
        
# 整合上面两个函数
def run(i, mutex):
        search(i)
        # 给买票环节加锁处理
        # 抢锁
        mutex.acquire()
        buy(i)
        # 释放锁
        mutex.release()
if __name__ == '__main__':
    # 在主进程中生成一把锁,让所有的子进程抢,谁先抢到谁先买票
    mutex = Lock()
    for i in range(1,11):
        p = Process(target=run, args=(i, mutex))
        p.start()
"""
注意:
	1.锁不要轻易的使用,容易造成死锁现象(我们写代码一般不会用到,都是内部封装好的)
	2.锁只在处理数据的部分加来保证数据安全(只在争抢数据的环节加锁处理即可) 
"""
9 进程间通信
9.1 队列Queue模块
创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
Queue([maxsize]) 
创建共享的进程队列。
参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
底层队列使用管道和锁定实现。
方法介绍
点击查看代码
Queue([maxsize]) 
创建共享的进程队列。maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。底层队列使用管道和锁定实现。另外,还需要运行支持线程以便队列中的数据传输到底层管道中。 
Queue的实例q具有以下方法:
q.get( [ block [ ,timeout ] ] ) 
返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,默认为True. 如果设置为False,将引发Queue.Empty异常(定义在Queue模块中)。timeout是可选超时时间,用在阻塞模式中。如果在制定的时间间隔内没有项目变为可用,将引发Queue.Empty异常。
q.get_nowait( ) 
同q.get(False)方法。
q.put(item [, block [,timeout ] ] ) 
将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。
q.qsize() 
返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。在某些系统上,此方法可能引发NotImplementedError异常。
q.empty() 
如果调用此方法时 q为空,返回True。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入新的项目。
q.full() 
如果q已满,返回为True. 由于线程的存在,结果也可能是不可靠的(参考q.empty()方法)。。
案例
点击查看代码
"""
管道:subprocess 
	stdin stdout stderr
队列:管道+锁
队列:先进先出
堆栈:先进后出
"""
------------------------------------------------------
from multiprocessing import Queue
# 创建一个队列
q = Queue(5)  # 括号内可以传数字,标示生成的队列最大可以同时存放的数据量
# 往队列中存数据
q.put(111)
q.put(222)
q.put(333)
print(q.full())   # 判断当前队列是否满了
print(q.empty()) #  判断当前队列是否空了
q.put(444)
q.put(555)
print(q.full())   # 判断当前队列是否满了
q.put(666)      	#当队列数据放满了之后 如果还有数据要放程序会阻塞 直到有位置让出来 不会报错
"""
存取数据 存是为了更好的取
千方百计的存、简单快捷的取
"""
# 去队列中取数据
v1 = q.get()
v2 = q.get()
v3 = q.get()
v4 = q.get()
v5 = q.get()
print(q.empty())
V6 = q.get_nowait()    # 没有数据直接报错queue.Empty
v6 = q.get(timeout=3)  # 没有数据之后原地等待三秒之后再报错  queue.Empty
try:
    v6 = q.get(timeout=3)
    print(v6)
except Exception as e:
    print('一滴都没有了!')
v6 = q.get()   		 队列中如果已经没有数据的话,get方法会原地阻塞
9.2 IPC机制
进程间通信(Inter-Process Communication)
from multiprocessing import Queue, Process
"""
研究思路
    1.主进程跟子进程借助于队列通信
    2.子进程跟子进程借助于队列通信
"""
def producer(q):
    q.put('我是23号技师 很高兴为您服务')
def consumer(q):
    print(q.get())
if __name__ == '__main__':
    q = Queue()
    p = Process(target=producer,args=(q,))
    p1 = Process(target=consumer,args=(q,))
    p.start()
    p1.start()
9.3 生产者消费者模型
生产者消费者模型
 在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
为什么要使用生产者和消费者模式
 在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
"""
生产者:生产/制造东西的
消费者:消费/处理东西的
该模型除了上述两个之外还需要一个媒介
	生活中的例子:
	做包子的将包子做好后放在蒸笼(媒介)里面,买包子的去蒸笼里面拿
	厨师做菜做完之后用盘子装着给你消费者端过去
	生产者和消费者之间不是直接做交互的,而是借助于媒介做交互
	
生产者(做包子的) + 消息队列(蒸笼) + 消费者(吃包子的)
"""
10 线程理论
什么是线程
"""
进程:资源单位
线程:执行单位
将操作系统比喻成一个大的工厂,
那么进程就相当于工厂里面的车间,
而线程就是车间里面的流水线。
每一个进程肯定自带一个线程
再次总结:
	进程:资源单位(起一个进程仅仅只是在内存空间中开辟一块独立的空间)
	线程:执行单位(真正被cpu执行的其实是进程里面的线程,线程指的就是代码的执行过程,执行代码中所需要使用到的资源都找所在的进程索要)
	
进程和线程都是虚拟单位,只是为了我们更加方便的描述问题
"""
为何要有线程
"""
开设进程
	1.申请内存空间	耗资源
	2.“拷贝代码”   耗资源
开线程
	一个进程内可以开设多个线程,在用一个进程内开设多个线程无需再次申请内存空间操作
总结:
	开设线程的开销要远远的小于进程的开销
	同一个进程下的多个线程数据是共享的!!!
"""
我们要开发一款文本编辑器
	获取用户输入的功能
    实时展示到屏幕的功能
    自动保存到硬盘的功能
针对上面这三个功能,开设进程还是线程合适???
	开三个线程处理上面的三个功能更加的合理
如何使用

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号