• python ==》 并发编程之多线程


    一:什么是线程?

    回答:在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程。线程:顾名思义,就是一个流水线工作的过程,一条流水线必须属于一个车间一个车间的工作过程就是一个线程,车间负责把资源整合到一起,就是一个资源单位,而一个车间内至少有一条流水线,那么流水线的工作需要电源,电源就相当于cpu。所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。

    总结:简单的理解为: 进程就是一个车间, 线程就是这个车间里的流水线的工作流程。

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

    二:线程的创建开销小:

    创建进程的开销要远大于线程?

    如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个即cpu(单核cpu)

    一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程)

    创建一个进程,就是创建一个车间(申请空间,在该空间内建至少一条流水线)

    而建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小

    进程之间是竞争关系,线程之间是协作关系?

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

    三:线程与进程的区别:

    1.线程的地址空间是可共享的,每个进程有自己的独立的地址空间。

    2.一个进程中的线程直接接入他的进程的数据段,但是每个进程都有他们直接的从父进程拷贝过来的数据段

    3.一个进程内部的线程之间能够直接通信,进程之间必须使用进程间通信实现通信

    4.新的线程很容易被创建,新的进程需要从父进程复制

    5.一个进程中的线程间能够有相当大的控制力度,进程仅仅只能控制他的子进程

    6.改变主线程(删除,优先级改变等)可能影响这个进程中的其他线程,修改父进程不会影响子进程。

    四:为何要用多线程

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

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

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

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

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

    五:线程中相关的模块内容介绍。

    1.threading 模块介绍:

    multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍

    2.开启线程的两种方式:

    直接调用 And  继承调用

    直接调用:
    
    from threading import Thread  
    import os 
    def talk():
        print('%s is running'%os.getpid())
    
    if __name__ == '__main__':
        t = Thread(target = talk)
        t.start()
        print('')
    
    总结:Thread  开启线程,主程序后执行的。
    
    输出结果:
    8960 is running
    主
    
    Process finished with exit code 0
    
    
    继承调用:
    
    from threading import Thread
    import os 
    class Mythread(Thread):
        def __init__(self):
            super().__init__()    
            self.name= name
        def run(self):
            print('pid:%s name:[%s] is running'%(os.getpid(),self.name))
    
    if __name__ =='__main__':
        t = Mythread('aray)
        t.start()
        print('',os.getpid())
    
    
    输出结果:
    pid:15648 name:[aray]is running 
    主 15648
    
    Process finished with exit code 0
    线程的两种调用方式

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

    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
        '''
    1.谁开的速度快
    2.比较pid值
    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,因为同一进程内的线程之间共享进程内的数据
    3 同一进程内的线程共享该进程的数据?

    4.练习:模拟一个文编编辑器。就简单做一个大写化处理,并保存,写入文件。

    from threading import Thread
    import os 
    msg_l = []
    format_l = []
    
    def Listen():
        '''监听输入'''
        while True:  #循环,不至于输入一次内容就关闭页面
            msg = input('>>:').strip()
            if not msg: continue
            msg_l.append(msg)   #把要输入的内容,添加到空列表里。
    
    def Format():
        '''格式化输出'''
        while True:
            if msg_l:   #判断,只有msg_l 不为空,才往下执行. 就是要传数据上来。
                res = msg_l.pop()  #pop  队列输出
                format_l.append(res.upper())  #格式化之后的结果,添加到另一个新的空列表。
    
    def Hold():
        ''' 保存'''
        while True:
            if format_l:  #判断,如果有个内容,为True,才往下执行。
                with open('db.txt','a',encoding = 'utf-8') as f:   
                    res = format_l.pop()
                    f.write('%s 
    'res)
    
    if __name__ == '__main__':
        t1 = Thread(target = Listen)    
        t2 = Thread(target = Format)
        t3 = Thread(target = Hold)
    
        t1.start()
        t2.start()    
        t3.start()
    
    
    输出结果:
    >>: a
    >>: b
    >>: c
    这时,会把abc  以大写的形式,写入  db.txt 文件里面。
    文本编辑器

    5.线程的其他方法:

    isAlive(): 返回线程是否活动的
    getname(): 返回线程名
    setname():设置线程名

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

    from threading import Thread,currentThread,activeCount
    import  os,time,threading
    
    def talk():
        print('%s is running '%currentThread().getName())
        print(t.is_alive())
    
    if __name__ == '__main__':
        t = Thread(target=talk,name='aray')
        t.start()
        time.sleep(1)
        print(t.is_alive())
        print(threading.activeCount())
        print(len(threading.enumerate()))
        print('',activeCount())
    
    输出结果:
    aray is running 
    True
    False
    1
    11
    
    Process finished with exit code 0
    举例

    5.1:主线程等待机制:join

    from threading import Tread,currentTread
    import time
    
    def talk():
        time.sleep(2)
        print('%s is running '%currentThread().getName())
    
    if __name__ == '__main__':
        t1 = Thread(target = talk,name='aray')
        t1.start()
        print('主‘)
    
    注意:没有join 之前。
    输出结果:
    主
    aray is running 
    
    Process finished with exit code 0
    
    #加了join之后。
    from threading import Tread,currentTread
    import time
    
    def talk():
        time.sleep(2)
        print('%s is running '%currentThread().getName())
    
    if __name__ == '__main__':
        t1 = Thread(target = talk,name='aray')
        t1.start()
        t1.join()
        print('主‘)
    
    输出结果:
    aray is running 
    主
    
    Process finished with exit code 0
    join

    5.2:守护线程:daemon

    from threading import Thread,currentThread,activeCount
    from multiprocessing import  Process
    import  os,time,threading
    
    def talk():
        time.sleep(2)
        print('%s is running '%currentThread().getName())
        # print('%s is running '%os.getpid())
    if __name__ == '__main__':
        t2 = Process(target = talk)
        t3 = Process(target = talk)
        t4 = Process(target = talk)
        t2.daemon = True
        t3.daemon = True
        t4.daemon = True
        t2.start()
        t3.start()
        t4.start()
        print('',os.getpid())
    
    
    输出结果:
    主 14576
    
    Process finished with exit code 0
    daemon

    5.3:全局解释器锁

    import time
    from threading import Thread,Lock
    n = 100
    def work():
        mutex.acquire()
        global n
        temp = n
        time.sleep(0.5)
        n = temp -1
        mutex.release()
    
    if __name__ =='__main__':
        mutex = Lock()
        t_l = []
        for i in range(100):
             t = Thread(target = work)
             t_l.append(t)
             t.start()
        for t in t_l:
            t.join()
        print(n)
    
    输出结果:
    0
    
    Process finished with exit code 0
    GIL锁

    5.4:详解join

    1.没有加锁
    from threading import Thread,Lock
    import time
    
    n = 100
    def work():
        global n
        temp = n
        time.sleep(0.1)
        n =temp -1
    if __name__ == '__main__':
        mutex = Lock()
        start = time.time()
        for i in range(100):
            t = Thread(target=work)
            t.start()
            t.join()
            stop = time.time()
        print("%s  time: %s"%(n,stop-start))
    
    输出结果:
    0  time: 10.085442781448364
    
    Process finished with exit code 0
    
    2.加锁解决并发,竞争的问题。保证安全,但是所处理的时间也相应增加。
    n = 100
    def work():
        # time.sleep(2)
        global n
        mutex.acquire()
        temp = n
        time.sleep(0.2)
        n = temp -1
        mutex.release()
    if __name__ == '__main__':
        mutex = Lock()
        l_t =[]
        start =time.time()
        for i in range(100):
            t = Thread(target=work)
            l_t.append(t)
            t.start()
        for t in l_t:
            t.join()
        stop = time.time()
        print("%s  time: %s"%(n,stop-start))
    
    输出结果:
    0  time: 20.061145067214966
    
    Process finished with exit code 0
    
    3.用join解决并发,竞争的问题。   需要21s  
    n = 10
    def work():
        time.sleep(2)      #(注意join是全局的包括这里的2s)
        global n
        temp = n
        time.sleep(0.1)
        n = temp -1
    if __name__ == '__main__':
        mutex = Lock()
        l_t =[]
        start =time.time()
        for i in range(10):
            t = Thread(target=work)
            l_t.append(t)
            t.start()
            t.join()
        stop = time.time()
        print("%s  time: %s"%(n,stop-start))
    
    输出结果:
    0  time: 21.011882066726685
    
    Process finished with exit code 0
    
    4.用锁来解决并发,竞争的问题。
    n = 10
    def work():
        time.sleep(2)
        global n
        mutex.acquire()
        temp = n
        time.sleep(0.1)
        n = temp -1
        mutex.release()
    if __name__ == '__main__':
        mutex = Lock()
        l_t =[]
        start =time.time()
        for i in range(10):
            t = Thread(target=work)
            l_t.append(t)
            t.start()
        for t in l_t:
            t.join()
        stop = time.time()
        print("%s  time: %s"%(n,stop-start))    #只需要3s
    
    输出结果:
    0  time: 3.0060813426971436
    
    Process finished with exit code 0
    详解join
     小结:
    互斥锁是 锁 局部的. 对共享数据修改的那一部分。
    join 是 锁 全局的.

    5.5:多线程  and  多进程

    多进程:
    优点:可以利用多核
    缺点:开销大
    多线程:
    优点:开销小
    缺点:不可利用多核

    from multiprocessing import Process
    from threading import Thread
    import time
    def work():
        res = 0
        for i in range (10000000):
            res +=1
    if __name__ == '__main__':
        l = []
        start = time.time()
        for i in range(4):
            p = Process(target=work)
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop = time.time()
        print('%s'%(stop-start))
    
    
    输出结果:
    0.9400010108947754
    
    Process finished with exit code 0
    多进程
    from multiprocessing import Process
    from threading import Thread
    import time
    def work2():
        time.sleep(2)
    if __name__ == '__main__':
        l = []
        start = time.time()
        for i in range(400):
            p2 = Thread(target=work2)
            # p2 = Process(target=work2)
            l.append(p2)
            p2.start()
        for p2 in l:
            p2.join()
        stop = time.time()
        print('%s'%(stop-start))
    
    输出结果:
    2.048475742340088
    
    Process finished with exit code 0
    多线程

    多进程主要用于计算功能。

    多线程主要用户I/O机制中。

    5.6:死锁的场景   

    from threading import Thread,Lock,currentThread
    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(1)   #线程2  等 B 锁, 线程1 等 A锁  这样就形成死锁了。这里sleep 1  是为了保证线程2 拿到A锁
            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锁
    Thread-1 拿到B 锁
    Thread-1 拿到 B锁
    Thread-2 拿到 A锁
    
    注意:这里是卡主了,没有执行完毕。
    死锁场景

     5.7:递归锁:用来解决死锁的问题。(RLock)

    from threading import Lock,Thread,RLock
    import time
    
    mutexB=mutexA=RLock()
    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(1)   #线程2  等 B 锁, 线程1 等 A锁  这样就形成死锁了。这里sleep 1  是为了保证线程2 拿到A锁
            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锁
    Thread-1 拿到B 锁
    Thread-1 拿到 B锁
    Thread-1 拿到A 锁
    Thread-2 拿到 A锁
    Thread-2 拿到B 锁
    Thread-2 拿到 B锁
    Thread-2 拿到A 锁
    Thread-4 拿到 A锁
    Thread-4 拿到B 锁
    Thread-4 拿到 B锁
    Thread-4 拿到A 锁
    Thread-6 拿到 A锁
    Thread-6 拿到B 锁
    Thread-6 拿到 B锁
    Thread-6 拿到A 锁
    Thread-8 拿到 A锁
    Thread-8 拿到B 锁
    Thread-8 拿到 B锁
    Thread-8 拿到A 锁
    Thread-10 拿到 A锁
    Thread-10 拿到B 锁
    Thread-10 拿到 B锁
    Thread-10 拿到A 锁
    Thread-5 拿到 A锁
    Thread-5 拿到B 锁
    Thread-5 拿到 B锁
    Thread-5 拿到A 锁
    Thread-9 拿到 A锁
    Thread-9 拿到B 锁
    Thread-9 拿到 B锁
    Thread-9 拿到A 锁
    Thread-7 拿到 A锁
    Thread-7 拿到B 锁
    Thread-7 拿到 B锁
    Thread-7 拿到A 锁
    Thread-3 拿到 A锁
    Thread-3 拿到B 锁
    Thread-3 拿到 B锁
    Thread-3 拿到A 锁
    
    Process finished with exit code 0
    递归锁

    5.8:信号量: 也是一把锁     举个例子: 公厕   (Semaphore)

    from threading import Thread,Semaphore,currentThread
    import time,random
    sm = Semaphore(5)
    def task():
        sm.acquire()
        print('%s 小号'%currentThread().getName())
        time.sleep(random.randint(1,10))
        print('%s over'%currentThread().getName())
        sm.release()
    if __name__ == '__main__':
        for i in range(6):
            t = Thread(target=task)
            t.start()
    
    输出结果:
    Thread-1 小号
    Thread-2 小号
    Thread-3 小号
    Thread-4 小号
    Thread-5 小号
    Thread-2 over
    Thread-6 小号
    Thread-4 over
    Thread-6 over
    Thread-5 over
    Thread-3 over
    Thread-1 over
    
    Process finished with exit code 0
    公厕

    5.9:Event:同进程一样。

    event.isset() 返回event的状态值
    event.wait() 如果 event.isset() == False 将 阻塞线程
    event.set()  设置event的状态值为True,所有阻塞池的线程进入就绪状态,等待调度
    event.clear() 回复event的状态值为false
    

    红绿灯例子:

    from threading import Event,Thread,currentThread
    import time
    e = Event()
    def traffic_lights():
        time.sleep(2)
        e.set()
    def car():
        print('% s 等待'%currentThread().getName())
        e.wait()
        print('%s  行驶'%currentThread().getName())
    
    if __name__ == '__main__':
        for i in range(10):
            t = Thread(target=car)
            t.start()
        traffic_thread = Thread(target=traffic_lights)
        traffic_thread.start()
    
    
    输出结果:
    Thread-1 小号
    Thread-2 小号
    Thread-3 小号
    Thread-4 小号
    Thread-5 小号
    Thread-7 等待
    Thread-8 等待
    Thread-9 等待
    Thread-10 等待
    Thread-11 等待
    Thread-12 等待
    Thread-13 等待
    Thread-14 等待
    Thread-15 等待
    Thread-16 等待
    Thread-5 over
    Thread-6 小号
    Thread-13  行驶
    Thread-11  行驶
    Thread-10  行驶
    Thread-16  行驶
    Thread-9  行驶
    Thread-14  行驶
    Thread-12  行驶
    Thread-15  行驶
    Thread-7  行驶
    Thread-8  行驶
    Thread-4 over
    Thread-1 over
    Thread-3 over
    Thread-2 over
    Thread-6 over
    
    Process finished with exit code 0
    红绿灯等车

    检测链接例子:

    from threading import Event,Thread,currentThread
    import time
    e = Event()
    def  conn_mysql():
        count = 1
        while not e.is_set():
            if count > 3:
                raise ConnectionError('尝试链接的次数过多')
            print('第%s次尝试'%currentThread().getName())
            e.wait(1)
            count+=1
        print('%s开始链接'%currentThread().getName())
    
    def check_mysql():
        print('%s检测mysql'%currentThread().getName())
        time.sleep(2)
        e.set()
    
    if __name__ == '__main__':
        for i in range(2):
            t = Thread(target=conn_mysql)
            t.start()
        t = Thread(target=check_mysql)
        t.start()
    
    
    输出结果:
    第Thread-1次尝试
    第Thread-2次尝试
    Thread-3检测mysql
    第Thread-1次尝试
    第Thread-2次尝试
    第Thread-1次尝试
    第Thread-2次尝试
    Thread-1开始链接
    Thread-2开始链接
    
    Process finished with exit code 0
    链接

    6.0:定时器:

    from threading import Timer
    
    def hello(n):
        print('hello,world',n)
    
    t=Timer(3, hello, args=(123,))
    t.start()
    
    
    输出结果:等待3s
    hello,world 123
    
    Process finished with exit code 0
    

    6.1:线程Queue:

    import queue
    
    q= queue.Queue(4)  #先进先出
    q.put('first')
    q.put('second')
    q.put('third')
    q.put('fourth')
    
    print(q.get())
    print(q.get())
    print(q.get())
    print(q.get())
    
    输出结果:
    first
    second
    third
    fourth
    
    Process finished with exit code 0
    先进先出
    import queue
    
    q = queue.LifoQueue()  #先进后出
    q.put('first')
    q.put('second')
    q.put('third')
    q.put('fourth')
    
    print(q.get())
    print(q.get())
    print(q.get())
    print(q.get())
    
    输出结果:
    fourth
    third
    second
    first
    
    Process finished with exit code 0
    先进后出
    import queue
    
     q = queue.PriorityQueue()
     q.put((20,'a'))
     q.put((10,'b'))
     q.put((30,'c'))
    
     print(q.get())
     print(q.get())
     print(q.get())
    
    输出结果:
    (10, 'b')
    (20, 'a')
    (30, 'c')
    
    Process finished with exit code 0
    优先级队列
  • 相关阅读:
    新型监控告警工具prometheus(普罗米修斯)入门使用(附视频讲解)
    Nginx、OpenResty和Kong的基本概念与使用方法
    Kubernetes网络方案Flannel的学习笔记
    新型监控告警工具prometheus(普罗米修斯)的入门使用(附视频讲解)
    超级账本HyperLedger:Fabric nodejs SDK的使用(附视频讲解)
    超级账本HyperLedger:Fabric使用kafka进行区块排序(共识,附视频讲解)
    超级账本HyperLedger:Fabric Golang SDK的使用(附视频)
    超级账本HyperLedger:Fabric的Chaincode(智能合约、链码)开发、使用演示
    超级账本HyperLedger:Fabric源码走读(一):源代码阅读环境准备
    超级账本HyperLedger:Fabric从1.1.0升级到1.2.0
  • 原文地址:https://www.cnblogs.com/zhongbokun/p/7454733.html
Copyright © 2020-2023  润新知