python多线程讲解

python多线程讲解

  • 一、第一个demo
  • 二、查看线程的创建和运行过程
  • 三、多线程共享全局变量
  • 四、解决共享全局变量出现资源竞争--互斥锁
  • 五、创建互斥锁出现死锁的情况
  • 六、多线程udp聊天案例
  • 七、总结

一、第一个demo

  1. 在python中使用threading中的Thread模块来定义使用线程
  2. 使用方法如下:
    """第一个线程的多任务demo"""
    import threading
    import time
    
    
    def sing():
        for i in range(5):
            print('-----sing----')
            time.sleep(1)
    
    
    def dance():
        for i in range(5):
            print('----dance----')
            time.sleep(1)
    
    
    def main():
    	# 创建一个普通的对象,不会创建线程
        t1 = threading.Thread(target=sing)
        t2 = threading.Thread(target=dance)
        # 当调用启动的时候才是线程真正创建的开始
        t1.start()
        t2.start()
    
    
    # windows系统中必须要有下面的函数
    if __name__ == '__main__':
        main()
    

二、查看线程的创建和运行过程

  1. 在threading模块中可以用threading.enumerate()来查看正在运行的线程

    """查看线程的创建和运行过程,查看其那个先创建"""
    import threading
    import time
    
    
    def sing():
        for i in range(5):
            print('----sing----%d \n' % i)
            time.sleep(1)
    
    
    def song():
        for i in range(10):
            print('----song----%d \n' % i)
            time.sleep(1)
    
    
    def main():
        print('main函数执行后执行的线程:' + str(threading.enumerate()) + '\n')
        t1 = threading.Thread(target=sing)
        print('t1对象创建后执行的线程:' + str(threading.enumerate()) + '\n')
        t2 = threading.Thread(target=song)
        print('t2对象创建后执行的线程:' + str(threading.enumerate()) + '\n')
    
        t1.start()
        print('t1对象start后的线程数:\n' + str(threading.enumerate())+ '\n')
        t2.start()
        print('t2对象start后的线程数:\n' + str(threading.enumerate()) + '\n')
    
    
    if __name__ == '__main__':
        main()
    

三、多线程共享全局变量

  1. 全局变量和局部变量:一个在函数里面一个在函数外面

  2. 在函数里面修改全局变量的做法:使用global和不使用global(值获取的不用加global)

  3. 只要指向的是可变的都可以不使用global,不可变的数据:数字、元组、字符串;其他都可变

  4. 举例:

    """多线程共享全局变量"""
    import threading
    import time
    
    
    g_num = 100  # 这个要使用global
    list = [1,2]  # 这个在函数中执行的时候要是不改变他的指向就不需要使用global
    
    
    def test1():
        """改变全局变量"""
        global g_num
        g_num += 1
        list.append(1)
        print('-----test1----g_num:%d----' % g_num)
        print(f'list初始:[1,2]检查是否可以在不使用global的情况下使用全局变量:{list}')
    
    
    def test2():
        """打印全局变量g_num,如果共享则打印的全局变量为101"""
        global g_num
        print('----test2----g_num:%d----' % g_num)
        print(f'----test2----list:{list}----')
    
    
    def main():
        t1 = threading.Thread(target=test1)
        t2 = threading.Thread(target=test2)
    
        t1.start()
        time.sleep(1)  # 确保test1先执行
        t2.start()
        time.sleep(1)
    
        print('----in main g_num = %d----' % g_num)
    
    
    if __name__ == '__main__':
        main()
    

    python多线程讲解_第1张图片

  5. 但是共享全局变量也会出现一些问题:如:资源竞争的问题:请看一下实例:

    """多线程出现的问题:资源竞争  计算10000000+10000000"""
    import threading
    import time
    
    
    g_num = 0
    
    
    def test1(temp):
        global g_num
        for i in range(temp):
            g_num += 1
        print('----in test1 g_num = %d----' % g_num)
    
    
    def test2(temp):
        global g_num
        for i in range(temp):
            g_num += 1
        print('----in test2 g_num = %d----' % g_num)
    
    
    def main():
        # 当args传的数值增大,即每个线程循环加的次数增多的时候,会出现资源竞争的问题会出现相加的值与实际值不符
        t1 = threading.Thread(target=test1, args=(10000000, ))
        t2 = threading.Thread(target=test2, args=(10000000, ))
    
        t1.start()
        t2.start()
    
        time.sleep(5)
        print('----in main g_num=%d----' % g_num)
    
    
    if __name__ == '__main__':
        main()
    

    运行结果:
    在这里插入图片描述
    由此图可见:多线程中如果发生资源的竞争会导致计算的结果和实际的结果不符

    解决方法:
    互斥锁

四、解决共享全局变量出现资源竞争–互斥锁

  1. 当多线程几乎同时修改某一个共享数据的时候,需要进行同步控制,则可以使用互斥锁

  2. 互斥锁为资源引入状态:锁定/非锁定

  3. 当某个线程要修改共享资源的时候,现将资源状态改为锁定,其他线程不能修改,直到该线程释放资源,将资源状态改为非锁定。这样就会保证了多线程下共享数据的正确性。

  4. 互斥锁的使用方法:

    互斥锁:
    创建互斥锁: mutex = treading.Lock()
    锁定: mutex.acquire()
    解锁: mutex.release()
    获取锁是否上锁: mutex.locked()
    
    
  5. 用互斥锁:( 计算10000000+10000000):
    5.1 方法一:(体现不出多任务)
    执行结果:
    在这里插入图片描述

    """多线程出现的问题:资源竞争  解决方法一"""
    import threading
    import time
    
    
    g_num = 0
    
    
    # 创建互斥锁, 默认是没有上锁的
    mutex = threading.Lock()
    
    
    def test1(temp):
        global g_num
        # 上锁,如果之前没有被上锁,那么此时上锁成功
        # 如果之前已经被上锁了,那么此时会堵塞在这里,直到这个锁被解开位置
        # 只有这个循环做完了才会给解锁,让下一个加锁的线程运行
        mutex.acquire()
        for i in range(temp):
            g_num += 1
        # 解锁
        mutex.release()
        print('----in test1 g_num = %d----' % g_num)
    
    
    def test2(temp):
        global g_num
        mutex.acquire()
        for i in range(temp):
            g_num += 1
        mutex.release()
        print('----in test2 g_num = %d----' % g_num)
    
    
    def main():
        # 当args传的数值增大,即每个线程循环加的次数增多的时候,会出现资源竞争的问题会出现相加的值与实际值不符
        t1 = threading.Thread(target=test1, args=(10000000, ))
        t2 = threading.Thread(target=test2, args=(10000000, ))
    
        t1.start()
        t2.start()
    
        time.sleep(5)
        print('----in main g_num=%d----' % g_num)
    
    
    if __name__ == '__main__':
        main()
    

    5.2 方法二:
    执行结果:
    在这里插入图片描述

    """多线程出现的问题:资源竞争  解决方法二"""
    import threading
    import time
    
    
    g_num = 0
    
    
    # 创建互斥锁, 默认是没有上锁的
    mutex = threading.Lock()
    
    
    def test1(temp):
        global g_num
        # 上锁,如果之前没有被上锁,那么此时上锁成功
        # 如果之前已经被上锁了,那么此时会堵塞在这里,直到这个锁被解开位置
        for i in range(temp):
            # 只给相加的位置上锁,加完然后就解锁
            mutex.acquire()
            g_num += 1
            # 解锁
            mutex.release()
        print('----in test1 g_num = %d----' % g_num)
    
    
    def test2(temp):
        global g_num
    
        for i in range(temp):
            mutex.acquire()
            g_num += 1
            mutex.release()
        print('----in test2 g_num = %d----' % g_num)
    
    
    def main():
        # 当args传的数值增大,即每个线程循环加的次数增多的时候,会出现资源竞争的问题会出现相加的值与实际值不符
        t1 = threading.Thread(target=test1, args=(10000000, ))
        t2 = threading.Thread(target=test2, args=(10000000, ))
    
        t1.start()
        t2.start()
    
        time.sleep(5)
        print('----in main g_num=%d----' % g_num)
    
    
    if __name__ == '__main__':
        main()
    

五、创建互斥锁出现死锁的情况

  1. 在线程键共享多个资源的时候,如果两个线程分贝占有一部分资源并且同时等待对方的资源就会出现死锁的问题。
  2. 举例:
    """"多线程解决资源竞争问题时创建互斥锁出现的问题-->死锁"""
    import threading
    import time
    
    
    # 创建两个互斥锁
    mutexA = threading.Lock()
    mutexB = threading.Lock()
    
    
    class Mythread1(threading.Thread):
        def run(self):
            # 对mutexA上锁
            mutexA.acquire()
    
            # mutexA上锁后,延迟1s,等待另外一个线程把mutexB上锁
            print(self.name + '----do1----up----')
            time.sleep(1)
    
            # 此时会出现堵塞,因为这个mutexB已经被另外一个线程抢先锁上
            mutexB.acquire()
            print(self.name + '----do1----down----')
            mutexB.release()
    
            # 对mutexA解锁
            mutexA.release()
    
    
    class Mythread2(threading.Thread):
        def run(self):
            # 对mutexB上锁
            mutexB.acquire()
    
            # mutexB上锁后,延迟1s,等待另外一个线程把mutexA上锁
            print(self.name + '----do2----up----')
            time.sleep(1)
    
            # 此时会出现堵塞,因为这个mutexA已经被另外一个线程抢先锁上
            mutexA.acquire()
            print(self.name + '----do1----down----')
            mutexA.release()
    
            # 对mutexB解锁
            mutexB.release()
    
    
    
    def main():
        t1 = Mythread1()
        t2 = Mythread2()
        t1.start()
        t2.start()
    
    
    if __name__ == "__main__":
        main()
    
    运行结果:导致程序卡死
    python多线程讲解_第2张图片
解决方法:
银行家算法
添加超时时间

六、多线程udp聊天案例

"""udp多线程聊天器"""
import socket
import threading


def recv_msg(udp_socket_t):
    """接受数据"""
    # 接受数据
    while True:
        recv_data = udp_socket_t.recvfrom(1024)
        print(recv_data)


def send_msg(udp_socket_t, socket_ip, socket_prot):
    """发送数据"""
    # 发送数据
    while True:
        send_data = input('请输入要发送的数据:')
        udp_socket_t.sendto(send_data.encode('gbk'), (socket_ip, socket_prot))


def main():
    # 1.创建套接字
    udp_socket_t = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # 2.绑定ip和prot
    udp_socket_t.bind(("",7890))

    # 3.获取对方的ip和prot
    socket_ip = input('请输入对方的ip:')
    socket_prot = int(input('请输入对方的prot:'))

    # 4.使用多线程收发消息
    # 发送的线程
    send_t = threading.Thread(target=send_msg, args=(udp_socket_t, socket_ip, socket_prot, ))

    # 接受数据的线程
    recv_t = threading.Thread(target=recv_msg , args=(udp_socket_t, ))

    # 启动线程
    send_t.start()
    recv_t.start()

    # 5.关闭套接字
    # udp_socket_t.close()


if __name__ == '__main__':
    main()

七、总结

  1. 线程是操作系统调度的单位
  2. 线程在不考虑GIL的情况及下切换时需要的资源比较小
  3. 多线程中:如果主线程结束了所有的线程都会结束
在一个函数中对全局变量进行修改的时候,到底是否需要使用global呢?
说明: 要看是否对全局变量的指向进行了修改;如果修改了指向,即让全局变量指向一个新的地方,那么必须使用global;如果仅仅是修改了指向的空间中的数据,此时不是必须使用global

你可能感兴趣的:(python,#,多任务)