zoukankan      html  css  js  c++  java
  • pathon之多线程详解

    一、线程理论

    1、什么是线程
    线程指的是一条流水线的工作过程

    进程根本就不是一个执行单位,进程其实是一个资源单位--------将资源集合到一起:
    一个进程内自带一个线程,线程才是CPU上的执行单位

    2、进程VS线程
    1、同一进程内的线程们共享该进程内资源,不同进程内的线程资源肯定是隔离的
    2、创建线程的开销比创建进程要小的多(大概是100倍)

    把操作系统比做一座工厂进行比对:
    工厂=====》车间=====》流水线
    操作系统====》进程=====》线程


    开启一个进程,就是申请了一个内存空间,将产生的数据丢掉里面,而代码的运行就是线程
    多个进程内存空间彼此是隔离的,而同一个进程下的多个线程,共享该进程内的数据,分散于不同进程之间的线程数据是隔离的

    3、创建进程的开销远大于开启线程的开销:
    造进程要向操作系统发请求,由操作系统帮你申请一个内存空间,
    在一个进程再开启一个请求,不需要在申请内存空间,只需要告诉操作系统只需要执行代码即可

    创建进程的开销要远大于线程
    形象比喻:
    如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个即cpu(单核cpu)
    一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程)
    创建一个进程,就是创建一个车间(申请空间,在该空间内建至少一条流水线)
    而建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小

    进程之间是竞争关系,线程之间是协作关系
    形象比喻:
    车间直接是竞争/抢电源的关系,竞争(不同的进程直接是竞争关系,是不同的程序员写的程序运行的,迅雷抢占其他进程的网速,360把其他进程当做病毒干死)
    一个车间的不同流水线式协同工作的关系(同一个进程的线程之间是合作关系,是同一个程序写的程序内开启动,迅雷内的线程是合作关系,不会自己干自己)

    二、开启线程的两种方式

    方式一、

    直接调用线程类,指定开启的子进程函数

    #开启线程的方式一、
    # 常用方式:
    from multiprocessing import Process
    from threading import Thread
    import time
    
    def task(name):
        print('%s is running' %name)       #最先被打印出来
        time.sleep(3)
        print('%s is done' % name)        #最后被打印出来
    
    if __name__ == '__main__':      #线程可以不用__main__,但是开启进程必须用,因为开启进程为从新在导入模块
        # t=Thread(target=task,args=('egon',))
        t=Process(target=task,args=('egon',))
        t.start()                  #开启线程的速速很快,几乎向操作系统发起请求就被开启(不需要申请内存空间);而开启进程要向操作系统发起请求,申请内存空间,而这个时间已经够运行到下一行代码的了
        print('主线程')
    
    #开启线程打印结果:
    '''
    egon is running
    主线程
    egon is done
    '''
    
    #开启进程打印结果:
    '''
    主线程
    egon is running
    egon is done
    '''

    方式二、

    自定义一个线程类,并继承Thread类,然后调用自定义类

    #自定义一个类、但是还要用被人内置的方法名
    from multiprocessing import Process
    from threading import Thread
    import time
    
    class MyThread(Thread):
    
        def run(self):
            print('%s is running' %self.name)
            time.sleep(3)
    
    if __name__ == '__main__':
        t=MyThread()
        t.start()
        print('主线程')

    三、进程与线程对比

    #2、线程创建开销小-----因为其不需要申请内存空间
    # 线程的开启速度更快
    from threading import Thread
    from multiprocessing import Process
    import os
    
    def work():
        print('hello')
    
    if __name__ == '__main__':
        #在主进程下开启线程
        t=Thread(target=work)
        t.start()
        print('主线程/主进程')
        '''
        打印结果:
        hello                #子进程内的打印先被打印出来,因为子进程开启不需要申请内存空间,所以开启速度非常快
        主线程/主进程
        '''
    
        #在主进程下开启子进程
        t=Process(target=work)
        t.start()
        print('主线程/主进程')
        '''
        打印结果:
        主线程/主进程        #由于子进程的开启需要申请内存空间,需要时间,而代码的运行速度是很快的,所以'主线程/主进程' 会先与子进程的代码被打印出来
        hello
        '''
    线程的开销小,开启速度更快
    from threading import Thread
    import time,os
    import time
    x=100
    def task():
        global x
        x=0           #子线程内的修改,可以站在主线程的角度去看,确实变量的值也被修改了
    
    if __name__ == '__main__':
        t=Thread(target=task,)
        t.start()
        # time.sleep(3)    #模拟开启线程和线程进行修改数据的时间
        t.join()           #主线程等着子线程运行完毕
        print('主线程',x)
    同一进程内的多个线程共享该进程内的资源(数据)
    '''疑问线程有没有pid?
    线程有没有杀死的需求'''
    from threading import Thread
    from multiprocessing import Process
    import time,os
    '''同一个进程内,主线程和子线程的PID是一样的'''
    def task():
        print('%s is running' %os.getpid())
        time.sleep(3)
    
    if __name__ == '__main__':
        t=Thread(target=task,)
        t.start()
        print('主线程',os.getpid())
    
        '''开启多个子进程每个子进程都有不同的PID'''
        p1=Process(target=task)
        p2=Process(target=task)
        p1.start()
        p2.start()
        print('主线程、主进程',os.getpid())
    观察PID

    四、线程对象的其他方法

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

    2、threading模块提供的一些方法:
    threading.currentThread(): 返回当前的线程变量。
    threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
    threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
    3、线程对象其他方法的应用
    from threading import Thread,current_thread,active_count,enumerate
    import time,os
    
    def task():
        print('%s is running' %current_thread().name)    #在当前线程查看自己的线程名
        time.sleep(3)
        print('%s is done' % current_thread().getName()) #在当前线程查看自己的线程名current_thread().name等价于current_thread().getName()
    if __name__ == '__main__':
        t1=Thread(target=task,name='第一个线程')
        # t2=Thread(target=task,)
        # t3=Thread(target=task,)
        t1.start()
        # t2.start()
        # t3.start()
    
        # print(t1.is_alive())      #判断线程是否存活
        print(active_count())       #活跃的线程个数
        print(enumerate())          #把当前活跃的线程对象放到列表中,可以通过点来使用线程的属性
        current_thread().setName('修改后的主线程名')
        print('主线程',current_thread().name)       #查看主线程的名字,也可以修改主线程的名字

     

    五、守护线程

    守护线程会在什么时候死?
    守护线程会等到该进程内所有的非守护线程都结束才跟着结束
    守护线程其实守护的是整个进程的运行周期(进程内所有的非守护线程都运行完毕)
    强调:
    守护进程是父进程结束,守护的进程就随之结束
    from threading import Thread,current_thread
    import time
    
    def task():
        print('%s is running' %current_thread().name)
        time.sleep(3)
        print('%s is done' % current_thread().name)    #守护的线程已经结束,所以看不到
    
    if __name__ == '__main__':
        t1=Thread(target=task,name='第一个线程')
        t1.daemon = True
        t1.start()     #由于开的是线程的速度太开了,所以 print('%s is running' %current_thread().name)会被先打印出来
        print('主线程')
    
    '''
    打印结果:
    第一个线程 is running
    主线程
    
    '''
    
    
     守护进程要等所有的非守护进程结束才会结束,而不是主线程结束守护进程就结束
    from threading import Thread
    import time
    def foo():
        print(123)
        time.sleep(5)
        print("end123")        #不可能看到,原因是所有的非守护线程都已经运行完毕,自己也就随之结束,只有当睡的时间比非守护线程短才能看到
    
    def bar():
        print(456)
        time.sleep(3)
        print("end456")
    
    if __name__ == '__main__':
    
        t1=Thread(target=foo)
        t2=Thread(target=bar)
    
        t1.daemon=True
        t1.start()
        t2.start()
        print("main-------")
    
        '''
        123
        456
        main-------
        end456
        '''
    
    

    六、线程互斥锁

    同一时间只有一个人能拿到锁

    from threading import Thread,Lock
    import time
    
    mutex=Lock()
    x=100
    
    def task():
        global x
        mutex.acquire()     #大家都声明了全局变量,加锁让他们等,同一时间只有一个人拿到
        temp=x              #保证100个线程都拿到了x,即都拿到了初始值
        time.sleep(0.1)     #都睡了0.1秒,这个时间已经都后面99个线程起来,那么他们谁先谁后抢到锁就不一定了
        x=temp-1            #正常已改改为0
        mutex.release()
    
    
    if __name__ == '__main__':
        start=time.time()
        t_l=[]
        for i in range(100):         #用for循环是有先后顺序,真实可能是同时起来的
            t=Thread(target=task)
            t_l.append(t)
            t.start()         #速度太快了,几乎是start同时
        for t in t_l:
            t.join()
    
        print('',x)
        print(time.time()-start)

    七、死锁现象及解决方法递归锁

    死锁现象:

    死锁现象是由于锁套锁导致的,两个线程互相拿到对方的锁就都被锁死了
    '''死锁现象'''
    from threading import Thread,Lock,RLock
    import time
    mutexA=Lock()
    mutexB=Lock()
    
    class MyThread(Thread):
        def run(self):
            self.f1()
            self.f2()
    
        def f1(self):
            mutexA.acquire()
            print('%s 拿到了A锁' %self.name)
    
            mutexB.acquire()
            print('%s 拿到了B锁' %self.name)
            mutexB.release()
    
            mutexA.release()
    
        def f2(self):
            mutexB.acquire()
            print('%s 拿到了B锁' %self.name)
            time.sleep(0.1)
    
            mutexA.acquire()
            print('%s 拿到了A锁' %self.name)
            mutexA.release()
    
            mutexB.release()
    
    
    if __name__ == '__main__':
        for i in range(10):
            t=MyThread()
            t.start()
    
    
    '''
    Thread-1 拿到了A锁      #线程1首先抢到了A锁,还没有释放,其他人只有等线程1把A锁释放,才能抢到A锁
    Thread-1 拿到了B锁      #线程1又抢到了B锁,然后释放掉了B锁和A锁,那么其他人此时就可以争抢A锁,
    Thread-1 拿到了B锁      #线程1紧接着又拿到了B锁,睡了一会准备去抢A锁,但是此时A锁已经被被人抢走,所以就会在原地阻塞住
    Thread-2 拿到了A锁      #线程1释放的A锁被线程2抢到,然后线程2又想去抢A锁,但是此时B锁已经被线程1抢走,所以线程1就被阻塞住
    #这就导致线程1和线程2互相锁死的现象,我们采用递归锁来进行解决死锁现象
    死锁现象

    解决死锁现象

    from threading import Thread,Lock,RLock
    import time
    
    mutexA=Lock()
    mutexB=Lock()
    
    mutexA=mutexB=RLock()       #RLock递归锁来解决死锁现象,递归锁的特点是可以连续的acquire
    
    class MyThread(Thread):
        def run(self):
            self.f1()
            self.f2()
    
        def f1(self):
            mutexA.acquire()
            print('%s 拿到了A锁' %self.name)
    
            mutexB.acquire()
            print('%s 拿到了B锁' %self.name)
            mutexB.release()
    
            mutexA.release()
    
        def f2(self):
            mutexB.acquire()
            print('%s 拿到了B锁' %self.name)
            time.sleep(0.1)
    
            mutexA.acquire()
            print('%s 拿到了A锁' %self.name)
            mutexA.release()
    
            mutexB.release()
    
    
    if __name__ == '__main__':
        for i in range(10):
            t=MyThread()
            t.start()
    
        t1=MyThread()
        t1.start()
    
        t2=MyThread()
        t2.start()
    
        t3=MyThread()
        t3.start()
        print('')
    递归锁解决死锁现象

    八、信号量

    # from multiprocessing import Semaphore
    # 信号量也是一种锁
    from threading import Thread,Semaphore,current_thread
    import time,random
    
    # 同一时间并发执行的任务数
    # 而互斥锁同一时间只有一个正在运行
    sm=Semaphore(5)     #得到一个信号量的对象
    
    def go_wc():
        sm.acquire()
        print('%s 上厕所ing' %current_thread().getName())
        time.sleep(random.randint(1,3))
        sm.release()
    
    if __name__ == '__main__':
        for i in range(23):
            t=Thread(target=go_wc)
            t.start()
  • 相关阅读:
    Linux(Centos7)yum安装最新redis
    refresh table tablename ;MSCK REPAIR TABLE table_name;
    整个shuffle的流程图
    Vim简明教程
    centos vim 7.3 升级 + vim 简单配置文件
    Python——可变类型与不可变类型(即为什么函数默认参数要用元组而非列表)
    python——修饰符
    转——《将人性置之死地而后生》刘夙
    各学科领域入门书籍推荐
    python——关于Python Profilers性能分析器
  • 原文地址:https://www.cnblogs.com/sui776265233/p/9306288.html
Copyright © 2011-2022 走看看