• 并发编程之多线程


    一:什么是线程?

      线程是程序的执行线路,相当于一条流水线,其包含了程序的具体执行步骤,如果我们把操作系统比喻为一个工厂,进程就是车间,线程就是流水线。流水线的工作需要电源,电源就相当于cpu。

      多线程的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间。相当于一个车间有多条流水线,都共用一个车间的资源。

      所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),线程才是CPU的最小执行单位。

    二:线程和进程的关系  

      进程中包含了运行该程序需要的所有资源。每个进程一旦被创建,就默认开启了一条线程,称之为主线程,一个进程可以包含多个线程,进程包含线程,而线程依赖进程。

      区别:进程对于操作系统的资源耗费非常高,而线程相反非常低(比进程低10~100倍)

         在同一个进程,多个线程之间资源共享

    三:为什么使用线程?

      为了提高效率,多线程可以使CPU在一个进程内进行切换,从而提高CPU占用率。创建一个进程就是就相当于创建一个车间(申请空间,在空间至少一条流水线),而创建线程,就是在一个车间内造一条流水线,无需申请空间,所以创建开销小。

      另外,不同的进程直接是竞争关系,是不同的程序员写的程序运行的,迅雷抢占其他进程的网速,360把其他进程当作病毒杀死。而同一个进程的线程是合作关系,是同一个程序员写的程序,迅雷内的线程是合作关系,360不会自己杀死自己。

    四:开启线程的两种方式

      方式一:实例化Thread类

    from threading import Thread
    def task():
        print('task run')
    t1=Thread(target=task)
    t1.start()
    print('over')
    方式一

      方式二:继承Thread类,覆盖run方法

    from threading import Thread
    
    class MyThread(Thread):
        def run(self):
            print('子线程running')
    
    MyThread().start()
    print('over 2')
    方式二

    五:比较进程与线程的运行速度

    100个进程
    from multiprocessing import  Process
    import time
    
    def task():
        pass
    
    if __name__ == '__main__':
        start=time.time()
        ps=[]
        for i in range(100):
            p=Process(target=task)
            p.start()
            ps.append(p)
        for p in ps:
            p.join()
    
        print(time.time()-start)
    
    =======================================
    运行结果:
    4.929280757904053
    100个进程
    100个线程
    from threading import Thread
    import time
    
    def task():
        pass
    start=time.time()
    ts=[]
    for i in range(100):
        t=Thread(target=task)
        t.start()
        ts.append(t)
    for t in ts:
        t.join()
    print(time.time()-start)
    
    
    =============================================
    运行结果:
    0.033980369567871094
    100个线程
    from  threading import Thread
    from multiprocessing import Process
    import os
    def work():
        global n
        n=0
    
    if __name__ == '__main__':
        # n=100
        # p=Process(target=work)
        # p.start()
        # p.join()
        # print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100
    
    
        n=1
        t=Thread(target=work)
        t.start()
        t.join()
        print('',n) #查看结果为0,因为同一进程内的线程之间共享进程内的数据
    同一个进程内的线程共享该进程的数据

    六:线程相关的其他方法

    Thread实例对象的方法
      # isAlive(): 返回线程是否活动的。
      # getName(): 返回线程名。
      # setName(): 设置线程名。
    
    threading模块提供的一些方法:
      # threading.currentThread(): 返回当前的线程变量。
      # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
      # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

    七:守护线程

      无论是进程还是线程,都遵循:守护XXX会等待主XXX运行完毕后被销毁。

      需要强调的是:运行完毕并非终止运行

    对主进程来说:运行完毕指的是主进程代码运行完毕
    
    对主线程来说:运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕。

    详细解释:

      1.主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等待非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束。

      2.主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程运行完毕后才能结束。

    from threading import Thread
    import time
    
    def task():
        print("sub thread run....")
        time.sleep(3)
        print("sub thread over....")
    
    t = Thread(target=task)
    t.setDaemon(True)
    t.start()
    
    t = Thread(target=task)
    t.setDaemon(True)
    t.start()
    
    print("over!")
    
    
    =====================================
    运行结果:
    
    sub thread run....
    sub thread run....
    over!
    守护线程

    八:线程互斥锁

      当多个进程或多个线程需要同时修改同一份数据时,可能会造成数据的错乱,所以必须得加锁。

    import time
    from threading import Thread,Lock
    
    lock =Lock()
    a = 100
    def task():
        lock.acquire()
        global a
        temp = a - 1
        time.sleep(0.01)
        a = temp    #以上三行可以写为 a-=1,此处是为了放大这个运行过程
        lock.release()
    
    ts = []
    for i in range(100):
        t = Thread(target=task)
        t.start()
        ts.append(t)
    for t in ts:
        t.join()
    
    print(a)
    
    
    =========================================
    运行结果:0
    线程互斥锁

    九:信号量(Semaphore)

      信号量也是一种锁,特点是可以设置一个数据可以被几个线程(进程)共享

      与普通锁的区别:

        普通锁一旦加锁,则意味着这个数据在同一时间只能被一个线程使用

        信号量可以让这个数据在同一时间只能被多个线程使用

      使用场景:可以限制一个数据被同时访问的次数,保证程序的正常运行

      同进程一样:

      Semaphore管理一个内置的计数器,每当调用acquire()时内置计数器-1,调用release()时内置计数器+1,计数器不能小于0,当计数器小于0时,acquire()将阻塞线程直到其他线程调用release()。

    from threading import Semaphore,Thread,current_thread
    import time,random
    
    sem = Semaphore(3)
    
    def task():
        sem.acquire()
        print("%s run..." % current_thread())
        time.sleep(3)
        sem.release()
    
    for i in range(10):
        t = Thread(target=task)
        t.start()
    
    ===========================================
    运行结果(其结果是三个一组出来的):
    <Thread(Thread-1, started 12924)> run...
    <Thread(Thread-2, started 14004)> run...
    <Thread(Thread-3, started 1560)> run...
    <Thread(Thread-4, started 2324)> run...
    <Thread(Thread-5, started 12840)> run...
    <Thread(Thread-6, started 6324)> run...
    <Thread(Thread-8, started 1648)> run...
    <Thread(Thread-9, started 13920)> run...
    <Thread(Thread-7, started 7724)> run...
    <Thread(Thread-10, started 3724)> run...
    信号量

    守护进程的例子:

    import time ,random
    from multiprocessing import Process,JoinableQueue
    
    # 吃热狗
    def eat_hot_dog(name,q):
        while True:
            res=q.get()
            # if not res:
            #     print('吃完啦')
            #     break
            print('%s吃了%s'%(name,res))
            time.sleep(random.randint(1,2))
            # 记录对比状态,记录吃了多少个,当记录的吃的个数等于生产的个数的时候,就不再等待吃了
            q.task_done()
    
    # 做热狗
    def make_hot_dog(name,q):
        for i in range(1,6):
            time.sleep(random.randint(1,2))
            print('%s生产了第%s个热狗'%(name,i))
            res='%s的%s个热狗'%(name,i)
            q.put(res)
        # q.put(None)
    
    if __name__ == '__main__':
       q=JoinableQueue()
    
       # 生产者1
       c1=Process(target=make_hot_dog,args=('小仙女热狗店',q))
       c1.start()
    
       # 生产者2
       c2=Process(target=make_hot_dog,args=('张艺兴热狗店',q))
       c2.start()
    
       # 消费者
       e1=Process(target=eat_hot_dog,args=('思聪',q))
       e1.daemon=True
       e1.start()
       
       # 保证生产者全部生产完成
       c1.join()
       c2.join()
       # 保证队列里面的数据全部被处理了,明确生产方不会再生产数据了
       q.join()
    
    =================================================
    运行结果:
    小仙女热狗店生产了第1个热狗
    思聪吃了小仙女热狗店的1个热狗
    张艺兴热狗店生产了第1个热狗
    小仙女热狗店生产了第2个热狗
    思聪吃了张艺兴热狗店的1个热狗
    张艺兴热狗店生产了第2个热狗
    小仙女热狗店生产了第3个热狗
    思聪吃了小仙女热狗店的2个热狗
    张艺兴热狗店生产了第3个热狗
    思聪吃了张艺兴热狗店的2个热狗
    张艺兴热狗店生产了第4个热狗
    小仙女热狗店生产了第4个热狗
    思聪吃了小仙女热狗店的3个热狗
    张艺兴热狗店生产了第5个热狗
    小仙女热狗店生产了第5个热狗
    思聪吃了张艺兴热狗店的3个热狗
    思聪吃了张艺兴热狗店的4个热狗
    思聪吃了小仙女热狗店的4个热狗
    思聪吃了张艺兴热狗店的5个热狗
    思聪吃了小仙女热狗店的5个热狗
    思聪吃热狗
  • 相关阅读:
    python类内置方法之__call__
    selenium之python源码解读-webdriver继承关系
    Jmeter之JDBC类型组件
    Jmeter逻辑控制之if控制器
    Java连接MySQL Warning: Establishing SSL connection without server's identity verification is not recommended
    Python3 Windows服务器简单实现 手机访问
    如何在C语言 C++里面调用 DOS命令
    常用DOS命令(1) color,dir,copy,shutdown,mkdir(md),rmdir(rd),attrib,cd
    Java实现队列
    Java 实现 栈
  • 原文地址:https://www.cnblogs.com/liuxiaolu/p/10209517.html
Copyright © 2020-2023  润新知