• 并发编程----多线程


    多线程

    一, 开启多线程的两种方式

    # 方式一  引用模块中Thread类方法
    from threading import Thread
    import time
    
    def sayhi(name):
        time.sleep(2)
        print(f'{name} say hi')
    
    if __name__ == '__main__':
        t = Thread(target=sayhi, args=('麻花腾',))
        t.start()
        print('in 主线程')
    -----------------------------------------------------------
    # 方式二   自定义类,继承Thread类方法,重写run方法
    from threading import Thread
    import time
    class Sayhi(Thread):
        def __init__(self, name):
            super().__init__()
            self.name = name
        def run(self):
            time.sleep(2)
            print(f'{self.name} say hello')
    
    if __name__ == '__main__':
        t = Sayhi('孙悟空')
        t.start()
        print('in 主线程')
    

    二, 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

    # 开启速度对比
    from threading import Thread
    from multiprocessing import Process
    
    def sayhi(name):
        print(f'{name} say hi')
    
    if __name__ == '__main__':
        t = Thread(target=sayhi, args=('孙悟空',))
        p = Process(target=sayhi, args=('猪八戒',))
        t.start()
        p.start()
        print('in 主进程/主线程')
    # 孙悟空 say hi
    # in 主进程/主线程
    # 猪八戒 say hi
    # 线程的开启速度比进程的开启速度快
    
    # pid对比
    from threading import Thread
    from multiprocessing import Process
    import os
    
    def sayhi(name):
        print(f'{name}{os.getpid()} say hi')
    
    if __name__ == '__main__':
        t = Thread(target=sayhi, args=('子线程',))
        p = Process(target=sayhi, args=('子进程',))
        t.start()
        p.start()
        print('in 主进程/主线程', os.getpid())
    # 子线程12812 say hi
    # in 主进程/主线程 12812
    # 子进程12648 say hi
    # 在主进程下开启多个线程,每个线程都跟主进程的pid一样
    # 开多个进程,每个进程都有不同的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,因为同一进程内的线程之间共享进程内的数据
    
    # socket小练习
    # 多线程实现并发: server端
    from threading import Thread
    import socket
    
    server = socket.socket()
    server.bind(('127.0.0.1', 2019))
    server.listen(5)
    
    def action(conn):
        while 1:
            data = conn.recv(1024).decode('utf-8')
            print(data)
            conn.send(data.upper().encode('utf-8'))
    
    if __name__ == '__main__':
        while 1:
            conn, addr = server.accept()
            t = Thread(target=action, args=(conn,))
            t.start()
    ----------------------------------------------------------
    # client端
    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1', 2019))
    
    while 1:
        t_s = input('>>>').strip()
        if not t_s:
            continue
        client.send(t_s.encode('utf-8'))
        f_s = client.recv(1024).decode('utf-8')
        print(f_s)
    

    三, 线程相关的其他方法

    from threading import Thread
    import threading
    import time
    def sayhi(name):
        time.sleep(2)
        print(f'{name} say hi')
    
    if __name__ == '__main__':
        t = Thread(target=sayhi, args=('麻花腾',))
        t.start()
        # 线程对象的方法
        print(t.is_alive())  # 判断子线程是否存活
        print(t.getName())   # 返回线程名
        t.setName('线程11')  # 设置线程名
        print(t.getName())
    
        # threading模块的方法:
        print(threading.current_thread().name)  # 获取此线程对象,可以使用此对象
        print(threading.enumerate())  # 返回一个列表,放置的是所有线程对象
        print(threading.active_count())   # 获取活跃的线程个数,包括主线程
    

    四, 守护线程

    # 回顾守护进程
    from multiprocessing import Process
    import time
    def foo():
        print(123)  # 开启进程太慢了,还没来得及打印,主进程就结束了
        time.sleep(1)
        print("end123")
    
    def bar():
        print(456)
        time.sleep(3)
        print("end456")
    
    if __name__ == '__main__':
    
        p1 = Process(target=foo)
        p2 = Process(target=bar)
    
        p1.daemon = True
        p1.start()
        p2.start()
        print("main-------")
    # main-------
    # 456
    # end456
    
    # 守护线程
    from threading import Thread
    import time
    def foo():
        print(123)
        time.sleep(1)
        # 比非守护线程短,主线程等到所有非守护子线程结束才结束,此守护线程已经运行完毕
        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-------
    # end123
    # end456
    -----------------------------------------------------------
    from threading import Thread
    import time
    def foo():
        print(123)
        time.sleep(4)  
        # 比非守护线程长,非守护线程结束时主线程也结束,此守护线程也同时结束,后面不会打印
        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
    # 多线程是同一个空间,同一个进程
    # 主线程是进程空间存活在内存的必要条件
    # 主线程会等所有非守护子线程结束才能结束
    

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

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

    • 对主进程来说,运行完毕指的是主进程代码运行完毕

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

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

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

    五, 互斥锁,同步锁,锁

    • 多线程的同步锁与多进程的同步锁是一个道理,就是多个线程抢占同一个数据(资源)时,我们要保证数据的安全,合理的顺序
    • 不加锁抢占同一个资源的问题
    from threading import Thread
    import time
    x = 100
    
    def task():
        global x
        temp = x
        time.sleep(1)
        temp -= 1
        x = temp
    
    if __name__ == '__main__':
        t1 = Thread(target=task)
        t1.start()
        t1.join()
        print('主线程', x)  # 99
    ------------------------------------------------------
    from threading import Thread
    import time
    x = 100
    
    def task():
        global x
        temp = x
        time.sleep(1)
        temp -= 1
        x = temp
    
    if __name__ == '__main__':
        lst = []
        for i in range(100):
            t = Thread(target=task)
            t.start()
            lst.append(t)
        for el in lst:
            el.join()
        print('主线程', x)  # 99
        # 因为100个子线程近乎同时运行,同时取得temp=100,又分别对x赋值99
    
    • 加锁:
    from threading import Thread
    from threading import Lock
    import time
    
    x = 100
    def task(lock):
        lock.acquire()
        global x
        temp = x
        time.sleep(0.1)
        temp -= 1
        x = temp
        lock.release()
    
    if __name__ == '__main__':
        lock = Lock()
        lst = []
        for i in range(100):
            t = Thread(target=task, args=(lock,))
            t.start()
            lst.append(t)
        for el in lst:
            el.join()
        print('主线程', x)  # 0
    
    # 互斥锁与join的区别
    # 互斥锁随机抢锁,公平  join提前排好顺序,不公平   都是串行
    

    六, 死锁现象与递归锁

    • 进程也有死锁与递归锁,进程的死锁和递归锁与线程的死锁递归锁同理
    • 死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
    # 死锁现象
    from threading import Thread
    from threading import Lock
    import time
    lock_A = Lock()
    lock_B = Lock()
    class Sayhi(Thread):
        def run(self):
            self.f1()
            self.f2()
    
        def f1(self):
            lock_A.acquire()
            print(f'{self.name}拿到A锁')
            lock_B.acquire()
            print(f'{self.name}拿到B锁')
            lock_B.release()
            lock_A.release()
    
        def f2(self):
            lock_B.acquire()
            print(f'{self.name}拿到B锁')
            time.sleep(1)
            lock_A.acquire()
            print(f'{self.name}拿到A锁')
            lock_A.release()
            lock_B.release()
    if __name__ == '__main__':
        t1 = Sayhi()
        t1.start()
        t2 = Sayhi()
        t2.start()
        t3 = Sayhi()
        t3.start()
        print('in 主线程')
    # Thread-1拿到A锁
    # Thread-1拿到B锁
    # Thread-1拿到B锁
    # Thread-2拿到A锁
    # in 主线程
    # 程序夯住了
    
    • 解决方法: 递归锁R Lock
    # 递归锁是一把锁,锁上有counter变量,只要acquire一次,counter就加一,release一次,counter减一,只要counter不为0,其他线程不能抢
    from threading import Thread
    from threading import RLock
    import time
    
    lock_B = lock_A = RLock()
    class Sayhi(Thread):
        def run(self):
            self.f1()
            self.f2()
    
        def f1(self):
            lock_A.acquire()
            print(f'{self.name}拿到A锁')
            lock_B.acquire()
            print(f'{self.name}拿到B锁')
            lock_B.release()
            lock_A.release()
    
        def f2(self):
            lock_B.acquire()
            print(f'{self.name}拿到B锁')
            time.sleep(1)
            lock_A.acquire()
            print(f'{self.name}拿到A锁')
            lock_A.release()
            lock_B.release()
    if __name__ == '__main__':
        t1 = Sayhi()
        t1.start()
        t2 = Sayhi()
        t2.start()
        t3 = Sayhi()
        t3.start()
        print('in 主线程')
    

    七, 信号量Semaphore

    • 同进程一样
    • Semaphore管理一个内置的计数器,每当调用acquire()时内置计数器-1;调用release() 时内置计数器+1;计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()
    # 实例: 同时只有5个线程可以获得semaphore,即可以限制最大连接数为5
    from threading import Thread
    from threading import Semaphore
    from threading import current_thread
    import time
    import random
    
    sem = Semaphore(5)
    
    def go_public_wc():
        sem.acquire()
        print(f'{current_thread().getName()} 上厕所ing')
        time.sleep(random.randint(1, 3))
        sem.release()
    
    if __name__ == '__main__':
        for i in range(1, 21):
            t = Thread(target=go_public_wc, name=f'{i}号')
            t.start()
    
  • 相关阅读:
    codevs 1115 开心的金明
    POJ 1125 Stockbroker Grapevine
    POJ 2421 constructing roads
    codevs 1390 回文平方数 USACO
    codevs 1131 统计单词数 2011年NOIP全国联赛普及组
    codevs 1313 质因数分解
    洛谷 绕钉子的长绳子
    洛谷 P1276 校门外的树(增强版)
    codevs 2627 村村通
    codevs 1191 数轴染色
  • 原文地址:https://www.cnblogs.com/maqian/p/11984315.html
Copyright © 2020-2023  润新知