进程与线程:
进程的概念:
1、程序的执行实例称为进程。
2、每个进程都提供执行程序所需资源的集合。一个进程有一个虚拟地址空间、可执行代码、对系统对象的开放句柄、一个安全上下文、一个独特的进程标识符、环境变量、一个优先级类、最小和最大工作集大小,
以及至少一个执行线程。每个进程以一个线程开始,通常称为主线程,但可以从它的任何线程创建额外的线程。
3、程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;
进程是程序的一次执行活动,属于动态概念。
4、在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,
因此,进程就是为了在CPU上实现多道编程而提出的。
有了进程为什么还要线程?
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?
其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:
1、进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
2、进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
线程的概念:
1、线程是操作系统能够进行运算调度的最小单位,是一堆指令集。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
2、线程是执行上下文,它是CPU执行指令流所需的全部信息。
3、假设你正在读一本书,现在你想休息一下,但你希望能够回来,从你停下来的确切地点恢复阅读。一个实现的方法就是记下页码,行数和字数。所以你阅读书的执行上下文是这3个数字。
如果你有一个室友,而且她使用相同的技巧,她可以在你不使用的时候拿书,然后从她停止阅读的地方继续阅读。然后你可以收回它,从你原来的地方恢复它。
4、线程以同样的方式工作。CPU给了你一个错觉,那就是它在同时进行多个运算。它通过在每个计算上花费一点时间来实现这一点。它可以这样做,因为它为每个计算都有一个执行上下文。
就像你可以和朋友共享一本书一样,许多任务可以共享一个CPU。
5、在更高的技术层面上,执行上下文(因此线程)由CPU寄存器的值组成。
最后:线程不同于进程。线程是执行的上下文,而进程是与计算相关联的一堆资源。一个进程可以有一个或多个线程。
说明:与进程相关的资源包括内存页(进程中的所有线程对内存具有相同的视图)、文件描述符(例如,打开套接字)和安全凭据(例如启动进程的用户ID)。
进程与线程的区别:
1、线程共享创建它的进程的地址空间;进程有自己的地址空间。 2、线程直接访问进程的数据段;进程拥有父进程的数据段的自身副本。 3、线程可以直接与其他线程的过程;过程必须使用进程间通信与兄弟姐妹的过程。 4、很容易创建新线程;新进程需要重复父进程。 5、线程可以对相同进程的线程进行相当的控制;进程只能对子进程进行控制。 6、对主线程的更改(取消、优先级更改等)可能影响进程的其他线程的行为;对父进程的更改不会影响子进程。
Python threading模块:
直接调用:
import threading import time def sayhi(num): print("is number %s"%num) time.sleep(2) if __name__ == "__main__": t1 = threading.Thread(target=sayhi,args=(1,)) t2 = threading.Thread(target=sayhi,args=(1,)) t1.start() t2.start() print(t1.getName()) print(t2.getName())
输出:
is number 1
is number 1
Thread-1
Thread-2
继承调用:
import threading,time class Thread_class(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self): print("is run on %d"%self.num) time.sleep(2) if __name__ == '__main__': t1 = Thread_class(1) t2 = Thread_class(2) t1.start() t2.start()
输出:
is run on 1
is run on 2
Join 和 Daemon
一些线程做后台任务,如发送实时数据包,或进行定期的垃圾收集,或什么的。只有当主程序运行时,这些才有用,一旦另一个非守护进程线程退出,就可以把它们杀掉。
没有守护线程,您必须跟踪它们,并告诉它们在程序完全退出之前退出。通过将它们设置为守护线程,可以让它们运行并忘记它们,并且当程序退出时,任何守护线程都会自动终止。
thread.join() #等待线程执行完再向后执行
thread.setDaemon(True) #将设为守护线程
import threading,time def run(num): print("is run %d" %num) time.sleep(2) print("is ok") def main(): for i in range(5): t = threading.Thread(target=run,args=(i,)) t.start() t.join() print("thread is done %s"%str(t.getName())) m = threading.Thread(target=main,args=[]) m.setDaemon(True) #将main线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,m线程也会退出,由m启动的其它子线程会同时退出,不管是否执行完任务 m.start() m.join(timeout=5) 输出: is run 0 is ok thread is done Thread-2 is run 1 is ok thread is done Thread-3 s run 2
注意:守护线程在关闭时突然停止。它们的资源(如打开的文件、数据库事务等)可能无法正常释放。
线程锁(互斥锁Mutex)
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
import time import threading def addNum(): global num # 在每个线程中都获取这个全局变量 time.sleep(1) num -= 1 # 对此公共变量进行-1操作 print('--get num:', num) 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。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。
*注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁
加锁:
import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 print('--get num:',num ) time.sleep(1) lock.acquire() #修改数据前加锁 num -=1 #对此公共变量进行-1操作 lock.release() #修改后释放 num = 100 #设定一个共享变量 thread_list = [] lock = threading.Lock() #生成全局锁 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 )
GIL 与 Lock
Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 ,具体关系如下图:
RLock(递归锁)
import threading,time def run1(): print("grab the first part data") lock.acquire() global num num +=1 lock.release() return num def run2(): print("grab the second part data") lock.acquire() global num2 num2+=1 lock.release() return num2 def run3(): lock.acquire() res = run1() print('--------between run1 and run2-----') res2 = run2() lock.release() print(res,res2) if __name__ == '__main__': num,num2 = 0,0 lock = threading.RLock() for i in range(10): t = threading.Thread(target=run3) t.start() while threading.active_count() != 1: print(threading.active_count()) else: print('----all threads done---') print(num,num2)
Semaphore(信号量)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 。
import threading,time def run(n): semaphore.acquire() time.sleep(1) print("run the thread is %s"%n) semaphore.release() if __name__ == '__main__': num = 0 semaphore = threading.BoundedSemaphore(5) for i in range(20): t = threading.Thread(target=run,args=(i,)) t.start() while threading.active_count() != 1: pass else: print("___all thread is done___") print(num)
Timer
此类表示仅在经过一定数量的时间之后才能运行的操作。
def hello(): print("hello, world") t = Timer(30.0, hello) t.start() # after 30 seconds, "hello, world" will be printed
Event
事件是一个简单的同步对象,该事件表示内部标志和线程。可以等待标志设置,或设置或清除标志本身。event = threading.Event()
客户端线程可以等待标志被设置 event.wait()
服务器线程可以设置或重置 event.set() event.clear()
1、如果设置了标志,则等待方法不执行任何操作 2、如果标志已清除,等待将阻塞,直到它再次设置。3、任意数量的线程都可能等待相同的事件。
import threading,time import random def light(): if not event.isSet(): event.set() #wait就不阻塞 #绿灯状态 count = 0 while True: if count < 10: print('