python的线程和进程
1、线程的基本概念
概念
线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
************************
好处
-
(1)易于调度。
-
(2)提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。
-
(3)开销少。创建线程比创建进程要快,所需开销很少
线程常用方法
方法 | 注释 |
---|---|
start() | 线程准备就绪,等待CPU调度 |
setName() | 为线程设置名称 |
getName() | 获取线程名称 |
setDaemon(True) | 设置为守护线程 |
join() | 逐个执行每个线程,执行完毕后继续往下执行 |
run() | 线程被cpu调度后自动执行线程对象的run方法,如果想自定义线程类,直接重写run方法就行了 |
2、进程的基本状态及状态之间的关系
状态:运行、阻塞、挂起阻塞、就绪、挂起就绪
状态之间的转换:
-
(1)准备就绪的进程,被CPU调度执行,变成运行态;
-
(2)运行中的进程,进行I/O请求或者不能得到所请求的资源,变成阻塞态;
-
(3)运行中的进程,进程执行完毕(或时间片已到),变成就绪态;
-
(4)将阻塞态的进程挂起,变成挂起阻塞态,当导致进程阻塞的I/O操作在用户重启进程前完成(称之为唤醒),挂起阻塞态变成挂起就绪态,当用户在I/O操作结束之前重启进程,挂起阻塞态变成阻塞态;
-
(5)将就绪(或运行)中的进程挂起,变成挂起就绪态,当该进程恢复之后,挂起就绪态变成就绪态;
3、线程和进程的关系以及区别?
“电脑CPU有几个核心,就同时只能运行几个任务。没有真正意义上的并发,所谓见到的并发只是通过上下文切换,CPU运行速度快产生的,我们只是感觉不到。
全局解释器锁GIL限制了python用不了多个CPU,只能用一个CPU核心,防止多线程时数据错乱。”
** 进程和线程的关系:**
-
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
-
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
-
(3)处理机分给线程,即真正在处理机上运行的是线程
-
(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体.
进程与线程的区别:
-
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
-
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
-
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
-
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
进程与线程的详解:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319272686365ec7ceaeca33428c914edf8f70cca383000
4、进程间通信的方式?
-
(1)管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
-
(2)信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。
-
(3)消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。
-
(4)共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
-
(5)信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。
-
(6)套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
5、同步和互斥的区别:
-
当有多个线程的时候,经常需要去同步这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。当然,在把整个文件调入内存之前,统计它的计数是没有意义的。但是,由于每个操作都有自己的线程,操作系统会把两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工作。
-
所谓同步,是指散步在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。如果用对资源的访问来定义的话,同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
-
所谓互斥,是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。如果用对资源的访问来定义的话,互斥某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
6、实例
#原来用的单线程执行的方式:串行,运行完一个在继续下一个 import time def run(): time.sleep(1) #sleep 1秒 print('run...') start_time=time.time() #获取开始运行时间 for i in range(5): run() end_time=time.time() print(end_time-start_time) #以上代码会每隔1秒输出一次run... ,总运行时间是5秒多5.070008754730225
#启动多线程,并用t.join()方法,让主线程等待所有子线程执行完成后,在继续往下走代码 import threading #threading模块用于启动线程 from threading import Thread import time def run(): time.sleep(1) #sleep 1秒 print('run...') start_time=time.time() #获取开始运行时间 # threading.Thread() # 没导入这个时的写法:from threading import Thread threads=[] #创建一个空线程列表,用于存放每个子线程的 for i in range(20):#循环启动20个线程 t1=Thread(target=run,) #启动线程,多线程同时运行,thread启动的都是子线程,子线程运行run方法 threads.append(t1) #将产生的子线程存入到线程list中 t1.start() #运行 for t in threads: #循环获取所有子线程 t.join() #主线程等待所有子线程执行结束之后再继续向下走代码 end_time=time.time() #获取运行结束的时间 print(end_time-start_time) #以上代码会同时输出20个run...,总运行时间是1秒多1.029601812362671
多线程下载图片实例:
import requests,time,threading from hashlib import md5 res=[] #为多线程拿到返回值使用,否则多线程无法拿到返回值 def down_load_pic(url): #下载图片的url r=requests.get(url) file_name=md5(r.content).hexdigest() #把文件md5之后的字符串当做文件名 with open(file_name+'.jpg','wb') as fw: fw.write(r.content) print('%s下载完成'%file_name) res.append(file_name) return file_name urls=[ 'http://p2.so.qhimgs1.com/bdr/_240_/t0128ebf20d8a62ed1e.jpg', 'http://p1.so.qhimgs1.com/bdr/_240_/t01a44596706ed343dd.jpg', 'http://p2.so.qhimgs1.com/bdr/_240_/t01a04afe4928109a2e.jpg', 'http://p0.so.qhmsg.com/bdr/_240_/t01c301b9d7753c1c4f.jpg', 'http://p2.so.qhimgs1.com/bdr/_240_/t01b53912c4720a714e.jpg', 'http://p2.so.qhimgs1.com/bdr/_240_/t01548810ae247dbad2.jpg' ] start_time=time.time() # #单线程下载 # for url in urls: # down_load_pic(url) #多线程下载 for url in urls: t=threading.Thread(target=down_load_pic,args=(url,))#因为down_load_pic函数有参数,所以要写args传参,args=(url,):当只有一个参数时,后面要加逗号 t.start() while threading.active_count()!=1: pass end_time=time.time() print('下载完成,下载时间是%s'%(end_time-start_time)) print(res) #返回list,存放所有file_name
7. 守护线程
守护主线程,使用setDaemon(True)把所有的子线程都变成了主线程的守护线程,主线程执行完成之后,子线程立即结束。所以当主线程结束后,整个程序就退出了。
import threading import time def run(): time.sleep(5) print('run...') for i in range(100): puren = threading.Thread(target=run) puren.setDaemon(True)#设置子线程为守护线程 puren.start() print('over') #以上代码输出结果只有一个:over
8. 锁:Lock
是在多个线程同时去操作同一个数据的时候,可能会导致数据不正确。要把这个数据机加上锁,这样同时就只能有一个线程在操作这个数据了。
python2上需要手动加锁,python3不用了,程序会自动给加锁了
import threading count=0 lock=threading.Lock() #申请一把锁 def run(): global count with lock: #等同于下方注释的3行代码 count+=1 # lock.acquire() #加上锁 # count+=1 # lock.release() #释放,如果不释放就会产生死锁,一直等着 for i in range(10): t=threading.Thread(target=run) t.start() while threading.active_count()!=1: pass print(count) #输出结果是10
<线程锁与GIL的区别>:
- GIL是防止C语言的原生线程执行时互相冲掉数据,因数据是共享的;
- 线程锁是防止python代码执行时(在操作系统之上)互相冲掉数据;
- 因此在多线程中如果要同时修改同一数据,就要加锁
9. 多进程
import multiprocessing import time def run(): time.sleep(10) print('run..') if __name__=='__main__': for i in range(10): p=multiprocessing.Process(target=run) p.start() while multiprocessing.active_children():#活动的子进程,等待其他子进程运行结束 pass print(multiprocessing.cpu_count())#查看cpu的个数
多线程:
无法利用多个CPU核心
多进程:
可以利用多个CPU的核心
--------------------------------
如何选取多线程还是多进程:
IO密集型任务
IO消耗比较多的
适合用多线程
input output
磁盘IO
网络IO
CPU密集型任务
消耗CPU比较多的
适合用多进程
多线程与多进程参考博客:https://www.cnblogs.com/whatisfantasy/p/6440585.html