内容简述:
一:初始线程、进程
二:进程和线程的区别
三:线程的调用方式
四:线程安全与线程锁
五:同步和异步
六:队列
七:生产者和消费者模型
一:初始线程,进程
看图说话:
多任务: 操作系统可同时运行多个任务,每一个程序内存都是独立的
一边浏览器上网,一边看电影,一边打开WORD至少3个任务在运行还有 后台运行的任务 概述:对于操作系统来说,一个任务就是一个进程.例如:打开一个浏览器就是启动一个浏览器进程,打开两word就启动了两个word进程,有的进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、缩进等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)
进程
(Process)是一个正在执行的程序,以一个整体的形式暴露给操作系统管理,里面包含对各种资源的调用,内存的管理,网络接口的调用等。。。对各种资源管理的集合。所有在同一个进程里的线程是共享同一块内存空间的
线程
是进程中一个独立的控制单元,它被包含在进程之中。是操作系统最小的调度单位, 是一串指令的集合。 一个进程中至少有一个线程,一个进程中可以并发多个线程,每条线程并行执行不同的任务
备注:
进程要操作CPU,必须先创建一个线程,一个进程中至少有一个线程
二:进程和线程的区别
1-Threads share the address space of the process that created it; processes have their own address space.
线程共享创建它的进程的内存空间;进程有自己的内存空间是独立的
2-Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
线程可以直接访问进程的数据;进程拥有父进程的数据的副本
3-Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.
同一个进程的线程之间可以互相访问;两个进程想通信,须通过一个中间代理来实现
4-New threads are easily created; new processes require duplication of the parent process.
创建新线程很容易;创建新进程需要复制父进程。
5-Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.
一个线程可以控制和操作同一进程里的其他线程;进程只能操作子进程
6-Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.
对主线程的更改(取消、优先级更改等)可能会影响进程中其他线程的行为;对父进程的更改不会影响子进程。
三:线程的调用方式
1-函数式调用
2-继承式调用
【线程调用方式-见课堂示例】
四:线程安全与线程锁
多线程中,所有变量都由所有线程共享。所以任何一个变量都可以被任意一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时修改一个变量,造成数据混乱问题。
解决方式:线程锁(互斥锁)
互斥锁
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
互斥锁为资源引入一个状态:锁定/非锁定。
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
hreading模块中定义了Lock类,可以方便的处理锁定:
lock= threading.Lock() #创建锁
lock.acquire([blocking]) #锁定
lock.release() #释放
其中,锁定方法acquire可以有一个blocking参数。
如果设定blocking为True,则当前线程会堵塞,直到获取到这个锁为止(默认)
如果设定blocking为False,则当前线程不会堵塞
[互斥锁代码-见示例]
上锁解锁过程
当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。
小结:
锁的优点:确保了某段关键代码只能由一个线程从头到尾完整地执行
锁的缺点:
1- 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率降低。
2-由于可以存在多个锁不同的线程持有不同的锁,并试图获取对方持有锁时,可能会造成死锁
五:同步和异步
同步:
协同步调,按预定的先后次序进行运行。如:买票排队。
多任务,多个任务之间执行的时候要求有先后顺序,必须一个先执行完成之后,另一个才能继续执行, 只有一个主线。简言之,同步意味着有序
异步:
一方的动作不用等另一方动作结果。如:不同窗口买票。
多任务, 多个任务之间执行没有先后顺序,可以同时运行,执行的先后顺序不会有什么影响,存在的多条运行主线。简言之,异步意味着无序。
六:队列
1.队列:先进先出 栈:先进后出
Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先进先出)队列Queue,LIFO(先进后出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语(原子性,即要么全做,要么全不做),能够在多线程中直接使用。可以使用队列来实现线程间的同步。
相当于有顺序的容器,那为啥要有队列?
列表和队列的根本区别是:取出数据后数据还在不在容器里。
class queue.Queue(maxsize=0) #先进先出
class queue.LifoQueue(maxsize=0) #last in fisrt out 先进后出
class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列
2.Queue的简要说明
put():添加数据到队列中
queue.Queue.put(item,block=True,timeout=None)
get():从队列中取数据
queue.Queue.get(block=True,timeout=None)
qsize():判断队列中是否有数据
empty():判断队列是否为空。如果为空返回True
full():如果满了返回True
put_nowait(item) 相当于 toput(item,False)
get_nowait() 相当于 toget(False).
task_done() 任务执行完毕
【几种队列用法-见课堂示例】
3.队列的主要作用
a.程序解耦
b.提高效率
七:生产者消费者模型
为啥要使用生产者和消费者模式?
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式?
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
【生产者和消费者模型-见课堂示例】