1、什么是进程(process)?(进程是资源集合)
1. 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,这种执行的程序就称之为进程
2. 程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念
3. 在多道编程中,我们允许多个程序同时加载到内存中,在 *** 作系统的调度下,可以实现并发地执行。
4. 进程的出现让每个用户感觉到自己独享cpu,因此,进程就是为了在cpu上实现多道编程而提出的。
5. 进程之间有自己独立的内存,各进程之间不能相互访问
6. 创建一个新线程很简单,创建新进程需要对父进程进行复制
多道编程: 在计算机内存中同时存放几道相互独立的程序,他们共享系统资源
单道编程: 计算机内存中只允许一个的程序运行
2、有了进程为什么还要线程?
1. 进程优点:
提供了多道编程,让我们感觉我们每个人都拥有自己的cpu和其他资源,可以提高计算机的利用率
2. 进程的两个重要缺点:
a. 第一点:进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
b. 第二点:进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
c. 例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息
d. 你会说, *** 作系统不是有分时么?分时是指在不同进程间的分时呀
e. 即 *** 作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀
3、什么是线程(thread)(线程是 *** 作系统最小的调度单位)
1. 线程是 *** 作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位
2. 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
3. 无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行
4. 进程本身是无法自己执行的,要 *** 作cpu,必须创建一个线程,线程是一系列指令的集合
5. 所有在同一个进程里的线程是共享同一块内存空间的,不同进程间内存空间不同
6. 同一个进程中的各线程可以相互访问资源,线程可以 *** 作同进程中的其他线程,但进程仅能 *** 作子进程
7. 两个进程想通信,必须要通过一个中间代理
8. 对主线程的修改可能回影响其他子线程,对主进程修改不会影响其他进程因为进程间内存相互独立,但是
同一进程下的线程共享内存
4、进程和线程的区别
启动一个线程比启动一个进程快,运行速度没有可比性。
先有一个进程然后才能有线程。
1、进程包含线程
2、线程共享内存空间
3、进程内存是独立的(不可互相访问)
4、进程可以生成子进程,子进程之间互相不能互相访问(相当于在父级进程克隆两个子进程)
5、在一个进程里面线程之间可以交流。两个进程想通信,必须通过一个中间代理来实现
6、创建新线程很简单,创建新进程需要对其父进程进行克隆。
7、一个线程可以控制或 *** 作同一个进程里面的其它线程。但进程只能 *** 作子进程。
8、父进程可以修改不影响子进程,但不能修改。
9、线程可以帮助应用程序同时做几件事
5、进程和程序的区别
1. 程序只是一个普通文件,是一个机器代码指令和数据的集合,所以,程序是一个静态的实体
2. 而进程是程序运行在数据集上的动态过程,进程是一个动态实体,它应创建而产生,应调度执行因等待资
源或事件而被处于等待状态,因完成任务而被撤消
3. 进程是系统进行资源分配和调度的一个独立单位
4.一个程序对应多个进程,一个进程为多个程序服务(两者之间是多对多的关系)
5. 一个程序执行在不同的数据集上就成为不同的进程,可以用进程控制块来唯一地标识每个进程
二、多线程
Python多线程编程中常用方法:
1、join()方法:如果一个线程或者在函数执行的过程中调用另一个线程,并且希望待其完成 *** 作后才能执行,
那么在调用线程的时就可以使用被调线程的join方法join([timeout]) timeout:可选参数,线程运行的最长时间
2、isAlive()方法:查看线程是否还在运行
3、getname()方法:获得线程名
4、setDaemon()方法:主线程退出时,需要子线程随主线程退出,则设置子线程的setDaemon()
1、线程2种调用方式:直接调用(函数调用), 继承式调用(类调用)
import threadingimport timedef sayhi(num): # 定义每个线程要运行的函数 print("running on number:%s" % num) time.sleep(3)#1、target=sayhi :sayhi是定义的一个函数的名字#2、args=(1,) : 括号内写的是函数的参数t1 = threading.Thread(target=sayhi, args=(1,)) # 生成一个线程实例t2 = threading.Thread(target=sayhi, args=(2,)) # 生成另一个线程实例t1.start() # 启动线程t2.start() # 启动另一个线程print(t1.getname()) # 获取线程名print(t2.getname())直接调用
class HMTHread(threading.Thread): # 如果想给自定义线程类中传递参数 需要重写init方法 def __init__(self, name): # 子类必须调用父类的同名init方法 threading.Thread.__init__(self) self.name = name # 任务1 def work1(self): print(threading.current_thread()) print("工作1...") # 任务2 def work2(self): print(self.name) print(threading.current_thread()) print("工作2...") # 程序员需要实现一个方法run方法 def run(self): print(threading.current_thread()) self.work2() self.work1()if __name__ == '__main__': # 如果是单线程(无论是主线程还是子线程 同一时间只能执行一个任务) # 创建一个子线程 sub_thread = HMTHread("自定义线程") # 启动线程(无论使用系统线程 还是自定义线程 都需要启动线程) sub_thread.start()继承式调用
2、for循环同时启动多个线程
(PS:下面利用for循环同时启动50个线程并行执行,执行时间是3秒而不是所有线程执行时间的总和)
import threadingimport timedef sayhi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3)for i in range(50): t = threading.Thread(target=sayhi,args=('t-%s'%i,)) t.start()开启多个线程
3、t.join(): 实现所有线程都执行结束后再执行主线程
说明:在4中虽然可以实现50个线程同时并发执行,但是主线程不会等待子线程结束在这里我们可以使用t.join()指定等待某个线程结束的结果
import threadingimport timestart_time = time.time()def sayhi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3)t_obJs = [] #将进程实例对象存储在这个列表中for i in range(50): t = threading.Thread(target=sayhi,args=('t-%s'%i,)) t.start() #启动一个线程,程序不会阻塞 t_obJs.append(t)print(threading.active_count()) #打印当前活跃进程数量for t in t_obJs: #利用for循环等待上面50个进程全部结束 t.join() #阻塞某个程序print(threading.current_thread()) #打印执行这个命令进程print("----------------all threads has finished.....")print(threading.active_count())print('cost time:',time.time() - start_time)t.join() 主线程等待子线程
4、setDaemon(): 守护线程,主线程退出时,需要子线程随主线程退出
import threadingimport timestart_time = time.time()def sayhi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3)for i in range(50): t = threading.Thread(target=sayhi,args=('t-%s'%i,)) t.setDaemon(True) #把当前线程变成守护线程,必须在t.start()前设置 t.start() #启动一个线程,程序不会阻塞print('cost time:',time.time() - start_time)守护线程
注:因为刚刚创建的线程是守护线程,所以主线程结束后子线程就结束了,运行时间不是3秒而是0.01秒
5、GIL锁和用户锁(Global Interpreter Lock 全局解释器锁
)
1.全局解释器锁:保证同一时间仅有一个线程对资源有 *** 作权限
作用:在一个进程内,同一时刻只能有一个线程通过GIL锁 被CUP调用,切换条件:I/O *** 作、固定时间(系统决定)
说明:python多线程中GIL锁只是在cpu *** 作时(如:计算)才是串行的,其他都是并行的,所以比串行快很多
1)为了解决不同线程同时访问同一资源时,数据保护问题,而产生了GIL
2)GIL在解释器的层面限制了程序在同一时间只有一个线程被cpu实际执行,而不管你的程序里实际开了多少条线程
3)为了解决这个问题,cpython自己定义了一个全局解释器锁,同一时间仅仅有一个线程可以拿到这个数据
4)python之所以会产生这种不好的状况是因为python启用一个线程是调用 *** 作系统原生线程,就是C接口
5)但是这仅仅是cpython这个版本的问题,在PyPy,中就没有这种缺陷
2. 用户锁:线程锁(互斥锁Mutex) :当前线程还未 *** 作完成前其他所有线程都无法对其 *** 作,即使已经释放了GIL锁
1. 在有GIL锁时为何还需要用户锁
1)GIL锁只能保证同一时间只能有一个线程对某个资源 *** 作,但当上一个线程还未执行完毕时可能就会释放GIL,其他线程就可以 *** 作了
2. 线程锁的原理
1)当一个线程对某个资源进行cpu计算的 *** 作时加一个线程锁,只有当前线程计算完成主动释放锁,其他线程才能对其 *** 作
2)这样就可以防止还未计算完成,释放GIL锁后其他线程对这个资源 *** 作导致混乱问题
import threading# 定义一个全局变量num = 0# 定义一个变量 保存互斥锁对象lock = threading.Lock()def work1(): global num for i in range(1000000): # 上锁 lock.acquire() num += 1 # 解锁 lock.release() print("work1:", num)def work2(): global num for i in range(1000000): # 上锁 lock.acquire() num += 1 # 解锁 lock.release() print("work2:", num)if __name__ == '__main__': # 创建两个子线程 work1_thread = threading.Thread(target=work1) work2_thread = threading.Thread(target=work2) # 启动线程 work1_thread.start() work2_thread.start()线程锁
1、使用线程锁解决上面问题的原理
1) 在GIL锁中再加一个线程锁,线程锁是用户层面的锁
2) 线程锁就是一个线程在对数据 *** 作前加一把锁,防止其他线程复制或者 *** 作这个数据
3) 只有这个线程对数据 *** 作完毕后才会释放这个锁,其他线程才能 *** 作这个数据
2、定义一个线程锁非常简单只用三步:
第一步: lock = threading.Lock() #定义一把锁
第二步: lock.acquire() #对数据 *** 作前加锁防止数据被另一线程 *** 作
第三步: lock.release() #对数据 *** 作完成后释放锁
6、死锁
1. 死锁定义
两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
2. 死锁举例
1. 启动5个线程,执行run方法,假如thread1首先抢到了A锁,此时thread1没有释放A锁,紧接着执行代码mutexB.acquire(),抢到了B锁,
在抢B锁时候,没有其他线程与thread1争抢,因为A锁没有释放,其他线程只能等待
2. thread1执行完func1函数,然后执行func2函数,此时thread1拿到B锁,然后执行time.sleep(2),此时不会释放B锁
3. 在thread1执行func2的同时thread2开始执行func1获取到了A锁,然后继续要获取B锁
4. 不幸的是B锁还被thread1占用,thread1占用B锁时还需要同时获取A锁才能向下执行,但是此时发现A锁已经被thread2暂用,这样就死锁了
from threading import Thread,Lockimport timemutexA=Lock()mutexB=Lock()class MyThread(Thread): def run(self): self.func1() self.func2() def func1(self): mutexA.acquire() print('3[41m%s 拿到A锁3[0m' %self.name) mutexB.acquire() print('3[42m%s 拿到B锁3[0m' %self.name) mutexB.release() mutexA.release() def func2(self): mutexB.acquire() print('3[43m%s 拿到B锁3[0m' %self.name) time.sleep(2) mutexA.acquire() print('3[44m%s 拿到A锁3[0m' %self.name) mutexA.release() mutexB.release()if __name__ == '__main__': for i in range(2): t=MyThread() t.start()# 运行结果:输出下面结果后程序卡死,不再向下进行了# Thread-1 拿到A锁# Thread-1 拿到B锁# Thread-1 拿到B锁# Thread-2 拿到A锁死锁
三、进程 1、进程间通信方式
1.利用Queues实现父进程到子进程(或子进程间)的数据传递
2.使用管道pipe实现两个进程间数据传递
3.Managers实现很多进程间数据共享
from multiprocessing import Process, Queuedef f(qq): # 将符进程中的q传递过来叫qq qq.put([42, None, 'hello']) # 此时子进程就可以使用符进程中的qif __name__ == '__main__': q = Queue() # 使用Queue()在父进程中定义一个队列实例q p = Process(target=f, args=(q,)) # 在父进程中起一个子进程 p,将父进程刚定义的q传递给子进程p p.start() print(q.get()) p.join()# 运行结果: [42, None, 'hello']Queue
from multiprocessing import Process, Pipedef f(conn): conn.send([42, None, 'hello']) # 3 子进程发送数据,就像socket一样 print("son process recv:", conn.recv()) conn.close()if __name__ == '__main__': parent_conn, child_conn = Pipe() # 1 生成一个管道实例,实例一生成就会生成两个返回对象,一个是管道这头,一个是管道那头 p = Process(target=f, args=(child_conn,)) # 2 启动一个子进程将管道其中一头传递给子进程 p.start() print(parent_conn.recv()) # 4 父进程收消息 # prints "[42, None, 'hello']" parent_conn.send('i am parent process') p.join()# 运行结果:# [42, None, 'hello']# son process recv: i am parent processpipe
from multiprocessing import Process, Managerimport osdef f(d, l): d[1] = '1' # 是个进程对字典放入的是同一个值,所以看上去效果不明显 l.append(os.getpID()) # 将这是个进程的进程ID放入列表中if __name__ == '__main__': with Manager() as manager: # 1 将Manager()赋值给manager d = manager.dict() # 2 定义一个可以在多个进程间可以共享的字典 l = manager.List(range(5)) # 3 定义一个可以在多个进程间可以共享的列表,默认写五个数据 p_List = [] for i in range(10): # 生成是个进程 p = Process(target=f, args=(d, l)) # 将刚刚生成的可共享字典和列表传递给子进程 p.start() p_List.append(p) for res in p_List: res.join() print(d) print(l)message 2、进程池
1. 进程池的作用就是限制同一时间可以启动进程的=数量
2. 进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进乘 ,那么程序就会等待,直到进程池中有可用进程为止。
3. 进程池中有两个方法:
1)apply: 多个进程异步执行,一个一个的执行
2)apply_async: 多个进程同步执行,同时执行多个进程
from multiprocessing import Process,Poolimport time,osdef foo(i): time.sleep(2) print("in the process",os.getpID()) #打印子进程的pID return i+100def call(arg): print('-->exec done:',arg,os.getpID())if __name__ == '__main__': pool = Pool(3) #进程池最多允许5个进程放入进程池 print("主进程pID:",os.getpID()) #打印父进程的pID for i in range(10): #用法1 callback作用是指定只有当Foo运行结束后就执行callback调用的函数,父进程调用的callback函数 pool.apply_async(func=foo, args=(i,),callback=call) #用法2 串行 启动进程不在用Process而是直接用pool.apply() # pool.apply(func=foo, args=(i,)) print('end') pool.close() #关闭pool pool.join() #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。进程池 3、僵尸进程
僵尸进程定义
1. 僵尸进程产生的原因就是父进程产生子进程后,子进程先于父进程退出
2. 但是父进程由于种种原因,并没有处理子进程发送的退出信号,那么这个子进程就会成为僵尸进程。
@H_106_403@
#!/usr/bin/env python#Coding=utf8 import os, sys, time#产生子进程pID = os.fork() if pID == 0: #子进程退出 sys.exit(0)#父进程休息30秒time.sleep(30)# 先产生一个子进程,子进程退出,父进程休息30秒,那就会产生一个僵尸进程defunct.py
###查看僵尸进程结果
[root@linux-node4 ~]# ps -ef| grep defunctroot 110401 96083 0 19:11 pts/2 00:00:00 python defunct.pyroot 110402 110401 0 19:11 pts/2 00:00:00 [python] <defunct>root 110406 96105 0 19:11 pts/3 00:00:00 grep --color=auto defunct
四、协程 1.1 什么是协程
1)协程微线程,纤程,本质是一个单线程
2)协程能在单线程处理高并发,因为遇到IO自动切换
线程遇到I/O *** 作会等待、阻塞
,协程遇到I/O会自动切换
(剩下的只有cpu *** 作)@H_403_430@线程的状态保存在cpu的寄存器和栈里而协程拥有自己的空间,所以无需上下文切换的开销,所以快
3)为甚么协程能够遇到I/O自动切换
4)协程拥有自己的空间,所以无需上下文切换的开销
1.2 协程优缺点@H_403_430@协程缺点
协程的本质是个单线程,它不能同时将 单个cpu 的多个核用上
,协程需要和进程配合才能运行在多cpu上@H_403_430@协程如果阻塞掉,整个程序都阻塞
@H_403_430@协程最大的优点
手动切换
,是一个C模块@H_403_430@gevent:对greenlet封装,遇到I/O自动切换
(借助C语言库greenlet
)@H_403_430@asyncio:和gevent一样,也是实现协程的一个模块(python自己实现
)from urllib import requestimport gevent,timefrom gevent import monkeymonkey.patch_all() #把当前程序所有的I/O *** 作给我单独做上标记def f(url): print('GET: %s' % url) resp = request.urlopen(url) data = resp.read() print('%d bytes received from %s.' % (len(data), url))#1 并发执行部分time_binxing = time.time()gevent.joinall([ gevent.spawn(f, 'https://www.python.org/'), gevent.spawn(f, 'https://www.yahoo.com/'), gevent.spawn(f, 'https://github.com/'),])print("并行时间:",time.time()-time_binxing)#2 串行部分time_chuanxing = time.time()urls = [ 'https://www.python.org/', 'https://www.yahoo.com/', 'https://github.com/', ]for url in urls: f(url)print("串行时间:",time.time()-time_chuanxing)# 注:为什么要在文件开通使用monkey.patch_all()# 1. 因为有很多模块在使用I / O *** 作时Gevent是无法捕获的,所以为了使Gevent能够识别出程序中的I / O *** 作。# 2. 就必须使用Gevent模块的monkey模块,把当前程序所有的I / O *** 作给我单独做上标记# 3.使用monkey做标记仅用两步即可: 第一步(导入monkey模块): from gevent import monkey 第二步(声明做标记) : monkey.patch_all()gevent
总结
以上是内存溢出为你收集整理的python 线程,进程,协程全部内容,希望文章能够帮你解决python 线程,进程,协程所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)