操作系统是用户和硬件沟通的桥梁

  操作系统,位于底层硬件与应用软件之间的一层
  工作方式:向下管理硬件,向上提供接口

操作系统进行切换操作: 把CPU的使用权切换给不同的进程。

  1、出现IO操作
  2、固定时间

 切换过程中就涉及到状态的保存,状态的恢复,资源利用等问题。 线程和进程在多语言之间通用。

二、进程和线程的概念(面试会用到)

  1、进程定义:

  进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。

  2、线程定义:

  线程是在进程中执行操作的单元,是进程中的一个实体,作为系统调度和分派的基本单位。线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。

  线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程是没有自己的系统资源,而是调用所在进程提供的系统资源。

三、进程与线程的关系

 1)进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。或者说进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。

  进程的特征:
  1.动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
  2.并发性:任何进程都可以同其他进程一起并发执行。
  3.独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
  4.异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。
 2)线程则是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

  线程的性质:
  1.线程是进程内的一个相对独立的可执行的单元。若把进程称为任务的话,那么线程则是应用中的一个子任务的执行。
  2.由于线程是被调度的基本单元,而进程不是调度单元。所以,每个进程在创建时,至少需要同时为该进程创建一个线程。即进程中至少要有一个或一个以上的线程,否则该进程无法被调度执行。
  3.进程是被分给并拥有资源的基本单元。同一进程内的多个线程共享该进程的资源,但线程并不拥有资源,只是使用他们。
  4.线程是操作系统中基本调度单元,因此线程中应包含有调度所需要的必要信息,且在生命周期中有状态的变化。
  5.由于共享资源【包括数据和文件】,所以线程间需要通信和同步机制,且需要时线程可以创建其他线程,但线程间不存在父子关系。

  3)进程和线程的关系:

  (1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
  (2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
  (3)CPU分给线程,即真正在CPU上运行的是线程。

  (4)每创建一个进程,就会相应的开辟一块固定的空间,在这个空间中存放程序,数据集,程序控制块,供线程进行调用!

 进程:是资源管理单位(容器)!由程序,数据集,进程控制块组成。每个进程之间一定是相互独立的!

 线程:是最小执行单位(被CPU执行的)   同一时刻,单核CPU中只能有一个线程在执行!

四、并行和并发

  并行处理(Parallel Processing)是计算机系统中能同时执行两个或更多个处理的一种计算方法。并行处理可同时工作于同一程序的不同方面。并行处理的主要目的是节省大型和复杂问题的解决时间。

  并发处理(concurrency Processing):指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机(CPU)上运行,但任一个时刻点上只有一个程序在处理机(CPU)上运行。

  并发的关键是你有处理多个任务的能力,不一定要同时。

  并行的关键是你有同时处理多个任务的能力。所以说,并行是并发的子集

Py修行路 python基础 (二十五)线程与进程

并发:多个线程被CPU依次执行
并行:多个CPU同时执行多个线程

在Cpython解释器中,由于有GIL锁,在一个进程中的多线程,每次只能竞争出一个线程由CPU执行!所以每个进程中的多线程是并发执行的,而不同的进程之间是并行执行的!(默认CPU为多核CPU)

五、同步和异步

 同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去。

  同步例子:打电话!给某人拨电话,你只有在对方接听了的情况下才会通话。在接听之前,你一直处于等待状态!

 异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

  异步例子:发短息就是异步通信。不管对方在什么状态,你把短信编辑好就可以直接发送过去。同时对方回复消息也是一个道理!


python有个特点,多进程下同一时刻只有一个线程在CPU执行!

python的多线程:由于GIL,导致同一时刻,同一进程只能有一个线程在CPU中被执行!

六、threading 模块

1、线程对象类的创建

  我们可以把当前文件执行看作一个主线程,然后在该文件下利用thread方法,实例出了两个子线程,同时在一个进程中执行!

Py修行路 python基础 (二十五)线程与进程

 

1)Thread类直接创建

import threading
import time

def tingge():
    print("听歌")
    time.sleep(3)
    print("听歌结束")

def xieboke():
    print("写博客")
    time.sleep(5)
    print("写博客结束")

t1 = threading.Thread(target=tingge)  #用类 创建线程
t2 = threading.Thread(target=xieboke)  #创建线程
#线程之间没有先后顺序,是竞争关系,谁快谁先执行
t1.start()  #执行线程
t2.start()  #执行线程

print("ending!")

2)Thread类继承方式创建

import threading
import time

class MyThread(threading.Thread):  #继承类
    def __init__(self,num):
        threading.Thread.__init__(self),
        self.num = num
    def run(self):  #重写run方法
        print("running on number:%s"%self.num)
        time.sleep(3)
        print(time.time()-s)

s = time.time()
t1 = MyThread(1)  #实例化一个线程
t2 = MyThread(2)

# s = time.time()
print("start!")
t1.start()     #执行线程
time.sleep(2)
print("--------------")
t2.start()
print("ending!")

2、Thread类的实例方法

1)join方法 当前子线程不执行结束,不执行主线程的操作
  t.join():线程对象t未执行完,会阻塞你的主线程。

2)t.setDaemon(True) 开启守护线程
  守护线程:将子线程的执行与主线程的执行绑定在一起,主线程结束执行完毕,子线程也会随着主线程结束;主线程不结束,子线程会一直在执行!

# join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

# setDaemon(True):
        '''
         将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。

         当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成

         想退出时,会检验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是只要主线程

         完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦'''
import threading
from time import ctime,sleep
import time

def Music(name):

        print ("Begin listening to {name}. {time}".format(name=name,time=ctime()))
        sleep(3)
        print("end listening {time}".format(time=ctime()))

def Blog(title):

        print ("Begin recording the {title}. {time}".format(title=title,time=ctime()))
        sleep(5)
        print('end recording {time}'.format(time=ctime()))


threads = []

t1 = threading.Thread(target=Music,args=('FILL ME',))
t2 = threading.Thread(target=Blog,args=('python',))

threads.append(t1)
threads.append(t2)

if __name__ == '__main__':
    #守护线程必须在start之前就设置,分析子线程设置了守护线程,整个程序的执行流程!
    # t1.setDaemon(True)  #守护线程
    # t2.setDaemon(True)

    for t in threads:
        t.start()
    #分析子线程添加了join方法,整个程序的执行流程!
    # for t in threads:
    #     t.join()
#与上边for循环的执行结果一致
    # t1.start()
    # t1.join()
    # t2.start()
    # t2.join()

    print ("all over %s" %ctime())

其他方法:

Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程变量。
  # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

七、GIL(全局解释器锁):

  1、只加载在 cpython 解释器的锁,阻止的是多线程并行!

  GIL:在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。
在调用任何Python C API之前,要先获得GIL
  GIL缺点:多处理器退化为单处理器;优点:避免大量的加锁解锁操作

 2、GIL的影响:

  无论你启多少个线程,你有多少个cpu, Python在执行一个进程的时候会淡定的在同一时刻只允许一个线程运行。所以,python是无法利用多核CPU实现多线程的。

  这样,python对于计算密集型的任务开多线程的效率甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

Py修行路 python基础 (二十五)线程与进程

计算密集型:一直在使用CPU
IO:存在大量IO操作

import time

def cal(n):  #定义一个自加函数
    sum=0
    for i in range(n):
       sum+=i

s=time.time()
#两种执行方式,函数自己调用和利用线程,比较执行时间,查看效率
# cal(50000000)   
# cal(50000000)

import threading

t1=threading.Thread(target=cal,args=(50000000,))
t2=threading.Thread(target=cal,args=(50000000,))

t1.start()
t2.start()
t1.join()
t2.join()

print("time",time.time()-s)

总结:

对于计算密集型的任务,python的多线程并没有用,但是对于IO密集型的任务:python的多线程是有意义的

python使用多核:开进程,弊端:开销大而且切换复杂

着重点:协程 + 多进程(使用简单,效率高)

方向:IO多路复用

终极思路:换C模块实现多线程

应用:爬虫

八、同步锁(互斥锁)(Lock)、死锁和递归锁(Rlock)

给线程加锁的原因

    我们知道,不同进程之间的内存空间数据是不能够共享的,试想一下,如果可以随意共享,谈何安全?但是一个进程中的多个线程是可以共享这个进程的内存空间中的数据的,比如多个线程可以同时调用某一内存空间中的某些数据(只是调用,没有做修改)。因此出于程序稳定运行的考虑,对于线程需要调用内存中的共享数据时,我们就需要为线程加锁。
  举例说明:初始:num = 0,线程每执行一次,希望num += 1。 
  加锁好处:在某一线程修改num的值时,即给该线程加锁,该线程加锁后,只要是该线程需要调用的代码以及涉及的内存空间,都会立即被锁上,比如"num+=1"!其它线程虽然也在并发同时执行,但是不能执行"num+=1"这行代码!即 不能够去访问或修改num这一个共享内存空间的数据,其他线程只能等待该线程 对代码执行访问修改完毕,解锁后才能去执行;当该线程解锁后,另一个线程马上加锁再来修改num的值,同时也不允许其它线程占用!如此类推,直到所有线程执行完毕。

 1)锁通常被用来实现对共享资源的同步访问。为避免多线程之间竞争产生的无序操作,为每一个共享资源创建一个Lock对象,相当于是对每个线程加上一把锁,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁。

    注意:同步锁(互斥锁)每次只能为一个对象(子线程)加锁,等待资源执行完成之后,才会释放锁!而从进程竞争出来的其他线程都必须等待锁释放之后才能去获取这个同步锁再执行资源代码。

#同步锁模版
import threading
R=threading.Lock()

R.acquire()
'''
对公共数据的操作
'''
R.release()
import time
import threading
lock = threading.Lock()  #创建同步锁

def SubNum():
    global num
    # print("ok")
    lock.acquire()  #获取加锁的对象
    temp=num        #对公共数据进行操作
    time.sleep(0.0001)
    #CPU执行的时间很短很短,加上时间让所有的线程都从进程GIL锁中竞争出来等待加锁,
    # 这样就人为的构成了一个有序的执行队列! 会明显发现不管时间是多少,代码执行完毕之后结果都是0
    num = temp - 1  #对公共变量进行-1操作
    lock.release()    #释放锁
num = 100      #设定一个共享变量

thread_list = []

for i in range(100):
    t = threading.Thread(target=SubNum)
    t.start()   #开启100个线程
    thread_list.append(t)

for t in thread_list:  #等待所有线程执行完毕
    t.join() #主线程阻塞,所有子线程执行完毕,才会执行主线程

print("Num is:",num)
同步锁

相关文章: