【python进阶 笔记】多任务-线程

【python高级基础 笔记】多任务-线程

目录

1.通过继承Thread方法创建线程 小实例

2. 多线程共享全局变量、同步、互斥锁

2.1. 用互斥锁解决资源竞争 小实例

2.2 UDP聊天器 (多任务版)


一些基础点:

  • thread模块是比较底层的模块,python的 threading模块 对thread做了一些包装,使用threading模块能完成多任务的程序开发。如 :t1 = threading.Thread(target=函数名,args=元祖) ,再通过 t1.start() 创建线程。(:args参数非必须,是作为函数的参数使用)
  •  注意:当调用Thread的时候,不会创建线程,当调用Thread创建出来的实例对象的start方法时,才会创建线程以及让这个线程开始运行。
  • 使用 threading.enumerate() 可以查看线程数量: length = len(threading.enumerate()) ,一般要配合时延,不然线程执行太快,无法查看。
  • 只有子线程执行完毕,主线程才能结束。
  • 为了让每个线程的封装性更好,所以使用threading模块时,往往会定义一个新的子类class,只要继承threading.Thread就可以了,然后重写run方法  (想干什么在run方法中定义)。通过 对象.start() 自动调用run方法。(适合于一个线程要做的事情比较复杂,且分成多个函数来做)
  • :一个对象.start() 只能启动一个线程。

1.通过继承Thread方法创建线程 小实例

import threading
import time

class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm "+self.name+' @ '+str(i) #name属性中保存的是当前线程的名字
            print(msg)


if __name__ == '__main__':
    t = MyThread()
    t.start()

# 输出:
# I'm Thread-1 @ 0
# I'm Thread-1 @ 1
# I'm Thread-1 @ 2

2. 多线程共享全局变量、同步、互斥锁

  • 在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据
  • 缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)
  • 如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确
  • 同步就是协同步调,按预定的先后次序进行运行。"同"字是指协同、协助、互相配合。  如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B执行,再将结果给A;A再继续操作。
  • 资源竞争可以通过线程同步来进行解决。最简单的同步机制即引入互斥锁
  • 互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

threading模块中定义了Lock类,可以方便的处理锁定:

  • 如果这个锁之前是没有上锁的,那么acquire不会堵塞
  • 如果在调用acquire对这个锁上锁之前 ,它已经被其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止
# 创建锁
mutex = threading.Lock()

# 锁定
mutex.acquire()

# 释放
mutex.release()

上锁解锁过程:

  • 当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。
  • 每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态
  • 线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

优点:确保了某段关键代码只能由一个线程从头到尾完整地执行

缺点:阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了;

           由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。造成应用的停止响应。

避免死锁:银行家算法(待续)、添加超时时间等。

2.1. 用互斥锁解决资源竞争 小实例

上锁:

  • 如果之前 没有被上锁,那么此时上锁成功;
  • 如果上锁之前 已经被上锁了,那么此时会堵塞在这里,直到这个锁被解开为止。

注:本例也可以将整个for循环上锁,但效率会低些。(  g_num += 1 该语句执行时有多个过程,加锁后保证+1的动作执行完毕)

import threading
import time

# 定义一个全局变量
g_num = 0


def test1(num):
    global g_num

    for i in range(num):
        mutex.acquire()  # 上锁
        g_num += 1
        mutex.release()  # 解锁
    print("-----in test1 g_num=%d----" % g_num)


def test2(num):
    global g_num
    for i in range(num):
        mutex.acquire()  # 上锁
        g_num += 1
        mutex.release()  # 解锁
    print("-----in test2 g_num=%d----" % g_num)


# 创建一个互斥锁,默认是没有上锁的
mutex = threading.Lock()


def main():
    t1 = threading.Thread(target=test1, args=(1000000,))
    t2 = threading.Thread(target=test2, args=(1000000,))

    t1.start()
    t2.start()

    # 等待上面的2个线程执行完毕....
    time.sleep(2)

    print("-----in main Thread g_num = %d---" % g_num)


if __name__ == "__main__":
    main()

执行结果:

-----in test1 g_num=1957071----
-----in test2 g_num=2000000----
-----in main Thread g_num = 2000000---

2.2 UDP聊天器 (多任务版)

线程1:接收数据然后显示

线程2:检测键盘数据然后通过udp发送数据

import socket
import threading


def recv_msg(udp_socket):
    """接收数据"""

    # 接收数据
    while True:
        recv_data = udp_socket.recvfrom(1024)
        print(recv_data)


def send_msg(udp_socket, dest_ip, dest_port):
    """发送数据"""
    while True:
        send_data = input("please input message:")
        udp_socket.sendto(send_data.encode("utf-8"), (dest_ip, dest_port))


def main():

    # 1 创建套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # 2 绑定本地信息
    udp_socket.bind(("", 8890))


    # 3 获取对方ip
    dest_ip = input("plseae input destination ip:")
    dest_port = int(input("plseae input destination port:"))

    # 4 创建2个线程,执行相应功能
    t_recv = threading.Thread(target=recv_msg, args=(udp_socket, ))
    t_send = threading.Thread(target=send_msg, args=(udp_socket, dest_ip, dest_port))
    
    t_recv.start()
    t_send.start()


if __name__ == "__main__":
    main()
发布了50 篇原创文章 · 获赞 10 · 访问量 6619

猜你喜欢

转载自blog.csdn.net/qq_23996069/article/details/104050575
今日推荐