这里介绍什么是线程,什么是进程,然后两者之间有什么关系,以及如何实现多线程和多进程。
线程
概念:线程是进程中执行运算的最小单位,是进程的一个实体,是被系统独立调度和分派的基本单位。线程不拥有自己的系统资源,只拥有一点在运行中必不可少的资源,但它可以与同属于一个进行的其它线程共享进程拥有的全部资源。一个线程可以撤销和创建另一个线程,同一个进程中可以有多个线程并发执行。
介绍了线程的概念,下面说一下线程有什么好处呢。
- 状态:运行、阻塞、挂起阻塞、就绪、挂起就绪
- 准备就绪的进程,被CPU调度执行,变成运行态;
- 运行中的进程,进行I/O请求或者不能得到所请求的资源,变成阻塞态;
- 运行中的进程,进程执行完毕(或时间片已到),变成就绪态;
- 将阻塞态的进程挂起,变成挂起阻塞态,当导致进程阻塞的I/O操作在用户重启进程前完成(称之为唤醒),挂起阻塞态变成挂起就绪态,当用户在I/O操作结束之前重启进程,挂起阻塞态变成阻塞态;
- 将就绪(或运行)中的进程挂起,变成挂起就绪态,当该进程恢复之后,挂起就绪态变成就绪态;
线程和进程的关系以及区别:
(1)进程和线程属于子集的关系,一个进程可以包含多个线程,但是至少有一个线程。
(2)同一个进程中的所有线程共享改进程的所有资源。
(3)处理机分给线程,即真正在处理机上运行的是线程,线程是真正的运行单位。
(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体.
线程和进程的区别:
调度 : 线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
并发性 : 不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
拥有资源 : 进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
系统开销 : 在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
进程间的通信方式:
管道 : 管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
信号 : 信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。
消息队列 : 消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。 今天即将学习rabbitmq.
共享内存 : 可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
信号量 : 主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。
套接字 : 这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
同步和互斥的区别:
当有多个线程的时候,经常需要去同步这些线程以访问同一个数据或资源。
------------------------------------------------------------------------------------------------
所谓同步,是指散步在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。如果用对资源的访问来定义的话,同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
------------------------------------------------------------------------------------------------
所谓互斥,是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。如果用对资源的访问来定义的话,互斥某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
Python版本的多进程和多线程实现模块:
Threading
Threading 用于提供线程相关操作:
# coding:utf-8
import threading
import time
def show(arg):
time.sleep(5)
print("thread+%s"%arg)
for i in range(10):
t = threading.Thread(target=show,args=(i,))
t.start()
print("main thread stop")
上述代码创建了10个“前台”线程,然后控制器就交给了CPU,CPU根据指定算法进行调度,分片执行指令。
方法:
start 线程准备就绪,等待CPU调度
setName 为线程设置名称
getName 获取线程名称
setDaemon 设置为后台线程或前台线程(默认);如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止;如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
join 逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
run 线程被cpu调度后自动执行线程对象的run方法
Lock 线程锁(互斥锁Mutex)
Event
守护进程
线程锁:一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
—这时候就要用到锁这个概念了
锁的使用:
创建锁
mutex = threading.Lock()
锁定
mutex.acquire([timeout])
释放
mutex.release()
代码:
def addNum():
global num #在每个线程中都获取这个全局变量
print('--get num:',num )
time.sleep(1)
num -=1 #对此公共变量进行-1操作
num = 100 #设定一个共享变量
thread_list = []
for i in range(100):
t = threading.Thread(target=addNum)
t.start()
thread_list.append(t)
for t in thread_list: #等待所有线程执行完毕
t.join()
print('final num:', num )
正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。
<线程锁与GIL的区别>:
GIL是防止C语言的原生线程执行时互相冲掉数据,因数据是共享的;
线程锁是防止python代码执行时(在操作系统之上)互相冲掉数据;
因此在多线程中如果要同时修改同一数据,就要加锁
Semaphore(信号量)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
Events线程间通信
Python提供了Event对象用于线程间通信,它是由线程设置的信号标志,如果信号标志位真,则其他线程等待直到信号接触。
Event对象实现了简单的线程通信机制,它提供了设置信号,清除信号,等待等用于实现线程间的通信。
Events的使用
event = threading.Event()
event.wait()
Event对象wait的方法只有在内部信号为真的时候才会很快的执行并完成返回。当Event对象的内部信号标志位假时,则wait方法一直等待到其为真时才返回。
event.set()
使用Event的set()方法可以设置Event对象内部的信号标志为真。Event对象提供了isSet()方法来判断其内部信号标志的状态。当使用event对象的set()方法后,isSet()方法返回真
event.clear()
使用Event对象的clear()方法可以清除Event对象内部的信号标志,即将其设为假,当使用Event的clear方法后,isSet()方法返回假