zoukankan      html  css  js  c++  java
  • python并发编程之多线程

    多线程

    线程

    1.什么是线程

    • 进程是一个执行空间 , 线程就是其中真正工作的单位 , 每一个进程至少有一个线程(如果我们把操作系统比喻为一个工厂 , 进程就是车间 , 线程就是流水线)

    进程包含了运行该程序所需要所有资源 , 进程是一个资源单位 , 线程是CPU的最小执行单位

    每一个进程一旦被创建 , 就默认开启了一条线程 , 称之为主线程

    2.为什么使用线程

    • 多线程指的是,在一个进程中开启多个线程,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点:

      1. 多线程共享一个进程的地址空间

      1. 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用

      1. 若多个线程都是CPU密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。

      1. 在多CPU系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)

    使用线程可以提高程序效率

    为何不用多进程提高效率 : 是因为进程对操作系统的资源耗费非常高

    3.线程与进程的区别:

    1. 线程共享创建它的进程的地址空间 ; 进程有自己的地址空间。

    2. 线程可以直接访问其进程的数据段;进程拥有自己父进程数据段的副本。

    3. 线程可以直接与其进程的其他线程通信;进程必须使用进程间通信来与兄弟进程通信。

    4. 新线程很容易创建;新流程需要复制父流程。线程可以对同一进程的线程进行相当大的控制;进程只能控制子进程。对主线程的更改(取消,优先级更改等)可能会影响进程的其他线程的行为;对父进程的更改不会影响子进程。

    4.什么时候开启多线程

    • 当程序中遇到IO操作时(当程序中时纯计算任务时 也无法提高效率)

    5.如何使用

    • from threading import Thread

    创建线程与创建进程的方式几乎一样 , 但是创建子进程会将父进程的资源复制执行一遍 , 所以必须在__main__下执行 , 而创建线程则不一样 , 线程间共享进程资源,所以不需要复制执行父线程代码,所以可以不加__main__

    一 . 创建线程的两种方式

    1. 调用类型

    from threading import Thread
    ​
    ​
    def task():
        print('thread running...')
    ​
    ​
    if __name__ == '__main__':
        t = Thread(target=task)
        t.start()
    ​
        print('主线程')
    ​
    # 执行结果
    # thread running...  (子线程比主线程执行速度更快)
    # 主线程
    1. 继承类型

    from threading import Thread
    ​
    ​
    class Sayhi(Thread):
        def __init__(self,name):
            super().__init__()
            self.name=name
        def run(self):
            print('%s say hello' % self.name)
    ​
    ​
    if __name__ == '__main__':
        t = Sayhi('jason')
        t.start()
        print('主线程')
        
        
    # 执行结果
    #jason say hello
    #主线程

    二 . 线程的常用方法

    Thread实例化对象的方法:
    # 1.isAlive():      判断线程是否还存在(未终止)
    # 2.getName():      返回线程名
    # 3.setName():      设置线程名
    ​
    threading模块下提供的方法
    # threading.currentThread():        返回当前的线程变量
    # threading.enumerate():        返回一个正在运行的线程列表
    # threading.activecount():      返回当前运行的线程数量
    # len(threading.activecount()):     与上一个方法返回相同值

    三 . 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

    1. 开启速度 : 开启线程>>开启进程(所以程序效率会大大提高)

    2. id :

      • 进程开启多线程的pid都相同 ( 很好理解pid是process id , 所以线程pid相同)

      • 进程开启多个进程的pid不同

    3. 空间资源:

      • 进程开启的多线程的共享进程内的资源 , 某线程修改数据后 . 其他线程再访问则是新的数据

      • 进程开启多个子进程的数据互相独立 , 子进程内修改数据不会对其他进程数据造成干扰

     

    守护线程

    守护线程(setdaemon)

    • 守护线程 : 守护线程会在所有非守护线程结束后结束

      守护线程本质是上是在守护主线程 ,但是对于主线程来说 , 运行完毕指的是主线程所在的进程内所有非守护进程统统运行完毕,主线程才算运行完毕

    from threading import Thread
    import time
    ​
    def task1():
        print('sub thread is running...')
        time.sleep(0.5)
        print('sub thread end...')
    def task2():
        time.sleep(0.1)
        print('task2 is run...')
    ​
    t1 = Thread(target=task1)
    t2 = Thread(target= task2)
    ​
    t1.setDaemon(True)  # 将t1设置为守护线程, 必须在start之前设置
    ​
    t1.start()
    t2.strat()
    ​
    # 执行结果
    #sub thread run...
    #task2 is run...
    ​
    ​

    线程互斥锁(Lock)

    • 什么时候用锁 :

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

    import time
    from threading import Thread,Lock
    ​
    lock =Lock()  # 实例化锁对象
    ​
    a = 100def task():
        lock.acquire()  # 给线程上锁
        global a  # 访问全局a
        temp = a - 1  # 修改全局a
        time.sleep(0.01)
        a = temp
        lock.release()   # 释放锁,线程执行完毕
    ​
    ts = []
    for i in range(100):
        t = Thread(target=task)
        t.start()
        ts.append(t)
    ​
    for t in ts:  # lock保证了多线程串行,同时主线程print(a)也在其中,但是我们想得到最终结果,所以用join人为设置顺序
        t.join()
    ​
    ​
    print(a)

    信号量(Semaphore)

    信号量:

    • 其实也是一种锁 , 特点是可以设置一个数据可以被几个线程(进程)共享.

      与普通锁的区别:

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

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

    • 使用场景

      • 可以限制一个数据同时访问的次数 , 保证程序正常运行

    from threading import Thread,Semaphore
    import time
    sem = Semaphore(3)  # 设置最大访问进程数
    ​
    ​
    def task():
        sem.acquire()
        print('你好啊')
        time.sleep(3)
        sem.release()
    ​
    ​
    for i in range(10):
        t = Thread(target=task)
    ​
        t.start()
        
    # 执行结果太长, 就不打印了
    # 现象描述 : 就是3个一次打印

    守护进程的使用

    """
    用生产者消费者模型实现一个顾客吃汉堡的功能
    主要是生产者生产处汉堡放入队列,然后消费者吃掉,
    要判断什么时候顾客吃完了所有生产了的汉堡
    """
    from multiprocessing import Process, JoinableQueue
    import time, random
    ​
    def eat_hotdog(name, q):
        while True:
            res = q.get()
            print('%s吃了%s' % (name,res))
            time.sleep(random.randint(1,2))
            q.task_done()
    ​
    ​
    def make_Hotdog(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)
    ​
    ​
    if __name__ == '__main__':
        q = JoinableQueue()
    ​
        #生产者
        c1 = Process(target=make_Hotdog, args=('a热狗店', q))
        c1.start()
    ​
        #生产者2
        c2 = Process(target=make_Hotdog, args=('b热狗店',q))
        c2.start()
    ​
        # 消费者
        p2 = Process(target=eat_hotdog, args=('顾客',q))
        p2.daemon = True  # 队列阻塞打开,主进程执行完毕,守护进程死
        p2.start()
    ​
    ​
        # 保证生产者全部完成
        c1.join()
        c2.join()
    ​
    ​
        # 保证队列中的数据全部被处理了
        '''
        join:阻止,直到队列中的所有项目都已获取并处理完毕。
    ​
        每当项目添加到队列时,未完成任务的计数就会增加。 
        每当消费者调用task_done()以指示该项目已被检
        索并且其上的所有工作都已完成时,计数就会下降。 
        当未完成任务的数量降至零时,join()取消阻塞。
        '''
        q.join()  # JoinableQueue方法

     

  • 相关阅读:
    Windows消息传递机制详解
    TCP、UDP、IP协议分析
    桥模式
    单例模式
    WPF属性学习
    第六章 数组与索引器 6.1一维数组的声明,创建与初始化
    C#委托与事件习题
    Windows窗体应用程序四(制作随机加法练习器)
    用VS制作简易计算器(WPF)
    第五章 5.3类的静态成员,析造函数与析构函数(猫类)
  • 原文地址:https://www.cnblogs.com/liusijun113/p/10211715.html
Copyright © 2011-2022 走看看