zoukankan      html  css  js  c++  java
  • 进程与线程

    系统基础

    操作系统的作用

    • 隐藏复杂的硬件接口,提供良好的抽象接口。

    • 管理、调度进程,并且将多个进程对硬件的竞争变得有序。

    多道技术

    • 产生背景:针对单核,实现并发(现在的主机一般是多核,那么每个核都会利用多道技术,但是核与核之间没有使用多道技术切换这么一说,一个程序io阻塞,会等到io结束再重新调度)

    • 时间上的复用(复用一个cpu的时间片)+空间上的复用(如内存中同时有多道程序)

    特点:多道----主存中存放两道或两道以上的程序;宏观上 并行----在一个时间段,它们都在同时执行,都处于执行的开始点和结束点之间;微观上串行----在某一时刻,他们在同一台计算机上交替、轮流、穿插地执行。

    进程

    进程实体:程序段、数据段、进程控制块(PCB)。

    **进程:**进程就是进程实体的一次执行过程。

    多进程(任务)并发执行:比如你现在使用电脑,一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,对于单核CPU来说,执行多任务是这样的:操作系统轮流让各个任务交替执行,任务1执行0.001秒,切换到任务2,任务2执行0.001秒,再切换到任务3,执行0.001秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

    进程并行:对于多核CPU的系统,如果是4核可以有四个任务并行执行。并行执行就是真正的同时执行,所以并行是并发的子集。

    总结:现在的操作系统很多任务执行都是并行+并发执行。

    线程

    有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。

    线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。

    进程与线程关系

    进程是最小的资源管理单元,线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。

    资源分配给进程,同一进程的所有线程共享该进程的所有资源。

    python中threading模块

    python中要在一个程序中开多个线程需要使用threading模块。

    线程创建的两种方式

    方式一:直接创建

    import threading
    import time
    def foo(n):
        print(">>>>>>>>%s" %n)
        time.sleep(n)
        print(threading.active_count())   #打印当前线程数目,结果为3,sleep后t2早已激活,还有一个是主线程
    
    def bar(n):
        print(">>>>>>>>%s" % n)
        time.sleep(n)
    t1 = threading.Thread(target=foo,args=(2,))    #创建一个线程对象,执行foo任务,元组形式传入参数(2,)
    t1.start()    #激活线程对象(调用run方法)
    print(threading.active_count())   ##打印当前线程数目,结果为2,因为t2还没有激活,还有一个是主线程
    t2 = threading.Thread(target=bar,args=(5,))
    t2.start()
    print("ending")
    

    方式二:从Thread继承,并重写run()

    import threading
    import time
    
    class MyThread(threading.Thread):
    
        def __init__(self,num):   #重写init方法,调用父类的init
            # threading.Thread.__init__(self)
            super(MyThread,self).__init__()
            self.num=num
    
        def run(self):    #重写run方法
            print("running on number:%s" %self.num)
            time.sleep(3)
    
    t1=MyThread(1)
    t2=MyThread(21)
    
    t1.start()
    t2.start()
    print("ending")
    

    Thread类的实例方法

    # start():  激活线程
    # join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
    
    # setDaemon(True):
     '''
     将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。
    
     当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成
    
     想退出时,会检验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是只要主线程
    
     完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦'''
    
      # isAlive(): 返回线程是否活动的。
      # getName(): 返回线程名。
      # setName(): 设置线程名。
    

    threading模块提供的一些方法:

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

    GIL锁

    Python中的线程是操作系统的原生线程,Python虚拟机使用一个全局解释器锁(Global Interpreter Lock)来互斥线程对Python虚拟机的使用。为了支持多线程机制,一个基本的要求就是需要实现不同线程对共享资源访问的互斥,所以引入了GIL。要知道GIL锁是在进程上的,即一个进程内线程无法在多核系统并行执行。最重要的是Cpython才有的GIL锁,大多数都是默认Cpython

    **GIL:**在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。 在调用任何Python C API之前,要先获得GIL

    GIL缺点:多处理器退化为单处理器;无法利用多核CPU实现多线程优点:避免大量的加锁解锁操作

    线程同步锁

    为什么要有同步锁呢?答案是可以解决数据安全一致性问题。如下示例:

    #未在数据处理段加锁,想得到最终num=0,执行结果却是94
    
    import threading
    import time
    num = 100
    def foo():
        global num
        temp = num
        time.sleep(0.0001)
        num = temp - 1
    l = []
    for i in range(100):
        threading.Thread(target=foo,args=()).start()
        # l.append(t)
    print(threading.active_count())
    while True:
        if threading.active_count() == 1:
            break
    # for x in l:
    #     x.join()
    print(num)
    

    #在数据处理段加锁之后得到理想结果num = 0
    
    import threading
    import time
    num = 100
    lock = threading.Lock()
    def foo():
        global num
        lock.acquire()
        temp = num
        time.sleep(0.0001)
        num = temp - 1
        lock.release()
    l = []
    for i in range(100):
        threading.Thread(target=foo,args=()).start()
        # l.append(t)
    print(threading.active_count())
    while True:
        if threading.active_count() == 1:
            break
    # for x in l:
    #     x.join()    #等待所有线程执行完毕
    print(num)
    

    当给线程处理内容加锁之后,只有当锁release释放之后其它线程才能去acquire获得这把锁,这样就能保证存取操作的唯一性, 从而保证同一时刻只有一个线程对共享数据存取。一般同步锁使用形式:

    import threading
    
    lock=threading.Lock()
    
    lock.acquire()
    '''
    对公共数据的操作
    '''
    lock.release()
    

    死锁与递归锁

    所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

    死锁

    #死锁示例:
    import threading
    import time
    lockA = threading.RLock()
    lockB = threading.RLock()
    class Mythread(threading.Thread):
        def __init__(self):
            super().__init__()
        def run(self):
            self.foo()
            self.bar()
        def foo(self):
            lockA.acquire()
            print('lock A',self.name)
            lockB.acquire()
            print('lock B',self.name)
            lockB.release()
            # time.sleep(0.2)
            lockA.release()
            # time.sleep(0.2)
        def bar(self):
            lockB.acquire()
            print('lock B',self.name)
            lockA.acquire()
            print('lock A',self.name)
            lockA.release()
            lockB.release()
    for i in range(10):
        t = Mythread()
        t.start()
    #上面这段程序就可能出现死锁,当然也有几率不会出现死锁,出现死锁的情况就是这样:
    当线程1的foo释放完B锁后,B锁被线程1的bar函数抢到,然后当线程1
    释放A锁后,A锁被线程2的foo函数抢到了,这样就会出现死锁。
    
    lock A Thread-1
    lock B Thread-1
    lock B Thread-1
    lock A Thread-2
    

    递归锁解决死锁现象

    import threading
    import time
    Rlock = threading.RLock()
    class Mythread(threading.Thread):
        def __init__(self):
            super().__init__()
        def run(self):
            self.foo()
            self.bar()
        def foo(self):
            Rlock.acquire()
            print('lock A',self.name)
            Rlock.acquire()
            print('lock B',self.name)
            Rlock.release()
            # time.sleep(0.2)
            Rlock.release()
            # time.sleep(0.2)
        def bar(self):
            Rlock.acquire()
            print('lock B',self.name)
            Rlock.acquire()
            print('lock A',self.name)
            Rlock.release()
            Rlock.release()
    for i in range(10):
        t = Mythread()
        t.start()
    

    在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁。

    信号量锁Semaphore

    Semaphore管理一个内置的计数器,

    每当调用acquire()时内置计数器-1;

    调用release() 时内置计数器+1;

    计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

    信号量锁示例:

    import threading
    import time
    
    semaphore = threading.Semaphore(5)   #设置同时可以有5个线程可以获得信号量锁
    
    def func():
        if semaphore.acquire():
            print (threading.currentThread().getName() + ' get semaphore')
            time.sleep(2)
            semaphore.release()
    
    for i in range(20):
      t1 = threading.Thread(target=func)
      t1.start()
    
    #最好执行结果,每两秒并发执行5个线程任务,即每2秒打印5次
    
  • 相关阅读:
    Solr4.7+Tomcat7.0配置
    Solr suggest 搜索建议功能 配置问题
    Solr 通过经纬度指定范围搜索
    Quartz.net 实例
    log4net简单实例
    依赖注入(Autofac)
    设计模式_状态模式_C#
    C# XML操作
    策略模式_C#_设计模式
    STM32随记
  • 原文地址:https://www.cnblogs.com/liao-lin/p/7208666.html
Copyright © 2011-2022 走看看