前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python线程回顾

python线程回顾

作者头像
小闫同学啊
发布2019-07-18 10:55:02
3990
发布2019-07-18 10:55:02
举报
文章被收录于专栏:小闫笔记小闫笔记

1线程

1.1 多任务

优点: 同时执行多个任务 提高程序的执行效率 用户的体验

  • 并发:基于时间片轮转执行多任务方式

在同一cpu上同一时间段内执行的多任务方式

  • 并行:基于多个CPU上同一时间点执行的多任务方式

1.2 线程概念

线程thread是一种实现多任务的手段

线程是运行的程序中一个执行流程 <分支/线索>

一个程序中默认存在一个线程 主线程mainthread, 新建的线程称为子线程

之前我们编写的所有的代码都是在主线程中运行的

1.3 创建线程

创建对象 对象 = threading.Thread(target=入口, args=(), kwargs={})

启动线程 对象.start()

存活线程列表 threading.enumerate()

阻塞等待子线程 对象.join()

代码语言:javascript
复制
import threading
import time


def sing():
    """唱歌"""
    for i in range(3):
        print("正在唱月亮之上....")
        time.sleep(1)
def dance():
    """跳舞"""
    for i in range(3):
        print("正在跳太空漫步....")
        time.sleep(1)
def main():
    """主线程"""
    # 创建子线程 1 创建出threading.Thread类的对象 2 对象.start()
    # target目标 执行子线程运行的入口函数
    # sing_thd = threading.Thread(target=dance()) # 错误的  
    sing_thd = threading.Thread(target=dance)
    sing_thd.start()

    sing()

if __name__ == '__main__':
    main()

thread类参数:

代码语言:javascript
复制
import threading
import time
def sing():
    """唱歌"""
    for i in range(3):
        print("正在唱月亮之上....")
        time.sleep(1)
def dance(dancer, address):
    """跳舞"""
    for i in range(3):
        print("%s正在%s跳太空漫步...." % (dancer, address))
        time.sleep(1)
def main():
    """主线程"""
    # 创建子线程 1 创建出threading.Thread类的对象 2 对象.start()
    # target目标 执行子线程运行的入口函数 ;
    # args是指定子线程函数运行所需的位置参数的元组
    # kwargs是指定子线程运行所需的 关键字参数的字典
    # sing_thd = threading.Thread(target=dance()) # 错误的  
    dance_thd = threading.Thread(target=dance, args=('Mike',), kwargs={'address': '月亮之上'})
    dance_thd.start()
    sing()
if __name__ == '__main__':
    main()

创建两个子线程:

代码语言:javascript
复制
import threading
import time
def sing(singer, address):
    """唱歌"""
    for i in range(3):
        print("%s正在%s唱月亮之上...." % (singer, address))
        time.sleep(1)
def dance(dancer, address):
    """跳舞"""
    for i in range(3):
        print("%s正在%s跳太空漫步...." % (dancer, address))
        time.sleep(1)
def main():
    """主线程"""
    # 创建子线程 1 创建出threading.Thread类的对象 2 对象.start()
    # target目标 执行子线程运行的入口函数 ;
    # args是指定子线程函数运行所需的位置参数的元组
    # kwargs是指定子线程运行所需的 关键字参数的字典
    # sing_thd = threading.Thread(target=dance()) # 错误的  
    dance_thd = threading.Thread(target=dance, args=('Mike',), kwargs={'address': '月亮之上'})
    dance_thd.start()
    # sing()
    sing_thd = threading.Thread(target=sing, args=('玲花',), kwargs={'address': '鸟巢'})
    sing_thd.start()
    # 了解 - 查看当前程序所有存活的线程列表
    print(threading.enumerate())
    time.sleep(5)
    print(threading.enumerate())

if __name__ == '__main__':
    main()

1.4 多线程执行的顺序是无序的 随机的

代码语言:javascript
复制
def task():
   for i in range(3):
       print("当前线程名称是:", threading.current_thread().name)
       time.sleep(1)
if __name__ == '__main__':
  for _ in range(5):
      sub_thread = threading.Thread(target=task)
      sub_thread.start()
#当前线程名称是: Thread-1
#当前线程名称是: Thread-2
#当前线程名称是: Thread-4
#当前线程名称是: Thread-3
#当前线程名称是: Thread-5
#当前线程名称是: Thread-1
#当前线程名称是: Thread-3
#当前线程名称是: Thread-5
#当前线程名称是: Thread-4
#当前线程名称是: Thread-2

1.5 主线程执行完成后不会立即退出而是等待子线程一起退出

代码语言:javascript
复制
# 结论:主线程创建出了子线程 主线程运行完代码后默认情况下不会直接退出 而是等待子线程运行完成后再一起退出
# 原因:主线程有义务回收子线程相应的资源

阻塞等待:

代码语言:javascript
复制
def main():
    """主线程"""
    # 创建子线程 1 创建出threading.Thread类的对象 2 对象.start()
    # target目标 执行子线程运行的入口函数 ;
    # args是指定子线程函数运行所需的位置参数的元组
    # kwargs是指定子线程运行所需的 关键字参数的字典
    # sing_thd = threading.Thread(target=dance()) # 错误的  
    dance_thd = threading.Thread(target=dance, args=('Mike',), kwargs={'address': '月亮之上'})
    dance_thd.start()
    # 阻塞等待 跳舞子线程运行完成 才会继续往下执行
    dance_thd.join()

    # sing()
    sing_thd = threading.Thread(target=sing, args=('玲花',), kwargs={'address': '鸟巢'})
    sing_thd.start()
    # 阻塞等待 唱歌子线程运行完成 才会继续往下执行
    sing_thd.join()

    print("感谢诛仙天雷滚滚")

上面的代码会先执行跳舞的操作,然后执行唱歌的操作,最后主线程等待子线程全部完成后,进行输出打印结果。

1.6 自定义线程类

上面的线程类thread类,不好维护

将线程类和函数功能封装在一起

面向对象特点:封装、多态、继承

优点: 将线程类 和线程的功能 封装类中

提高代码的可维护性

实现:

子类继承Thread类

实现子类中run方法-子线程入口

创建子类的对象

调用子类对象.start() 启动线程的创建和执行

代码语言:javascript
复制
class MyThread(threading.Thread):
   """子类 将Thread类和线程函数封装在一起  提高代码的可维护性"""
   # 如果需要在子线程 中运行某个代码 需要实现run方法
   def run(self):
       """run方法中存放就是 子线程需要运行的代码 一旦子线程启动运行 自动调用该方法"""
       print("这是子线程 %s" % (threading.enumerate()))
       self.sing()
   def sing(self):
       for i in range(5):
           print("这是子线程")
           time.sleep(1)
def main():
   """主线程 对象 = threading.Thread(); 对象.start() """
   mt = MyThread()
   mt.start()  # 创建并且启动子线程  调用run方法
   # mt.run()  # 这么编写代码 会执行run方法的代码 是在主线程中执行

1.7 daemon线程

需要需要子线程跟随主线程一起退出

把所有的子线程设置为daemon线程 然后一旦主线程执行完成 所有子线程全部立即都退出

设置

1.对象.setDaemon(True) # 在start之前调用

代码语言:javascript
复制
    sing_the.setDaemon(True)
    sing_the.start()

2. 对象.daemon = True # 在start之前调用

代码语言:javascript
复制
    kongfu_the.daemon = True
    kongfu_the.start()

3. Thread(,,,daemon=True) #只能用以Py3

代码语言:javascript
复制
kongfu_the = threading.Thread(target=kongfu,args=('小闫',),kwargs={'kongfu_name':'葵花宝典'},daemon = True)

1.8 资源竞争问题

多线程共享全局变量 无序

导致同时修改全局资源时产生混乱的现象-资源竞争 数据竞争

验证多线程是否共享全局变量;

代码语言:javascript
复制
import threading


g_number = 1000

def update_number():
    """子线程函数  修改 全局变量g_number值"""
    global g_number

    g_number += 1
    print("获取到全局变量的值是%d" % g_number)

def main():
    # 创建一个子线程 修改全局变量的值
    thd = threading.Thread(target=update_number)
    thd.start()

    # 阻塞等待子线程运行完成 
    thd.join()
    # 打印
    print("获取到全局变量的值是%d" % g_number)

    # 结论: 同一个进程内部的多个线程 是共享全局变量的


if __name__ == '__main__':
    main()

1.9 多线程同步

多线程同步 - 保持秩序

目的: 解决资源竞争问题

代码语言:javascript
复制
import threading


g_number = 0

def update_number1(lock):
    """子线程函数  修改 全局变量g_number值"""
    global g_number
    for i in range(1000000):
        # 2 修改全局变量之前 应该先申请锁  ; 如果锁已经被别人锁定了 那当前任务就会阻塞等待直接对方释放锁
        lock.acquire()
        g_number += 1
        # 3 修改完成之后  应该释放互斥锁
        lock.release()
def update_number2(lock):
    """子线程函数  修改 全局变量g_number值"""
    global g_number
    for i in range(1000000):
        # 2 修改全局变量之前 应该先申请锁  ; 如果锁已经被别人锁定了 那当前任务就会阻塞等待直接对方释放锁
        lock.acquire()
        g_number += 1
        # 3 修改完成之后  应该释放互斥锁
        lock.release()
def main():
    # 1 创建互斥锁对象mutex  Lock类
    lock = threading.Lock()

    # 创建一个子线程 修改全局变量的值
    thd1 = threading.Thread(target=update_number1, args=(lock,))
    thd1.start()
    thd2 = threading.Thread(target=update_number2, args=(lock,))
    thd2.start()
    # 阻塞等待子线程运行完成 
    thd1.join()
    thd2.join()
    # 打印
    print("获取到全局变量的值是%d" % g_number)

    # 结论: 同一个进程内部的多个线程 是共享全局变量的


if __name__ == '__main__':
    main()

1.10 互斥锁

互斥锁 是一种实现多线程同步的手段 最终目的为解决资源竞争

特点: 保证同一时刻只有一个任务能够成功占有锁

两种状态:锁定locked 和 未锁定

创建互斥锁对象

lock = threading.Lock()

申请互斥锁 - 阻塞等待

lock.acquire()

释放互斥锁

lock.release()

优点:

能够保证代码的正确执行 不会产生资源竞争问题

缺点:

降低了多任务执行的效率

容易造成死锁问题 deadlock

1.11 死锁问题

互斥锁没有被正确的释放 容易造成 死锁问题

可以考虑使用with自动的 锁的申请和释放

代码语言:javascript
复制
def get_value(index):
   # 上锁
   lock.acquire()
   print(threading.current_thread())
   my_list = [3,6,8,1]
   # 判断下标释放越界
   if index >= len(my_list):
       print("下标越界:", index)
       lock.release()
       return
   value = my_list[index]
   print(value)
   time.sleep(0.2)
   # 释放锁
   lock.release()

# 使用with能够 对互斥锁自动进行申请和释放 防止出错出现死锁 提高代码可读性
def get_value(index):
   with lock:
       print(threading.current_thread())
       my_list = [3,6,8,1]
       # 判断下标释放越界
       if index >= len(my_list):
           print("下标越界:", index)
           return
       value = my_list[index]
       print(value)
       time.sleep(0.2)

1.12 多任务UDP聊天工具

代码语言:javascript
复制
import socket
import threading


def send_message(udp_socket):
    """发送消息"""
    # 需要用户输入数据 收件人IP 端口
    data = input("数据:")
    IP = input("IP:")
    port = int(input("端口:"))
    udp_socket.sendto(data.encode(), (IP, port))


def recv_message(udp_socket):
    """接收消息"""
    # remote远程
    while True:
        recv_data, remote_address = udp_socket.recvfrom(1024)
        print("接收到来自%s的数据%s" % (str(remote_address), recv_data.decode()))

def main():
    # 1 创建UDP套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    udp_socket.bind(('', 8888))

    # 1.5 创建子线程 专门接收UDP数据
    recv_thd = threading.Thread(target=recv_message, args=(udp_socket,))
    recv_thd.daemon = True  # 让子线程跟随主线程一起退出
    recv_thd.start()

    # 2 开始循环接收用户的输入 1 发送数据 ; 2接收数据 3退出程序; 其他报错重新输入
    while True:
        op = input("1 发送数据\n2 接收数据\n3 退出 请选择:")
        if op == '1':
            send_message(udp_socket)
        # elif op == '2':
        #     recv_message(udp_socket)
        elif op == '3':
            print("Bye......")
            break
        else:
            print("输入错误 重新输入")

    # 3 退出循环后关闭套接字对象
    udp_socket.close()

if __name__ == '__main__':
    main()

1.13 TCP服务器轮流为多个客户端服务

代码语言:javascript
复制
import socket
import threading


def client_request(client_socket):
    """处理 客户端的请求"""
    while True:
        # 5 使用分机进行深入交流 echo回射
        recv_data = client_socket.recv(1024)

        print("接收到了数据:%s" % recv_data.decode())
        client_socket.send(recv_data)
        # client_socket.send(recv_data.decode('gbk').encode())
        if not recv_data:
            print("客户端下线了")
            break

    # 6 分机挂机
    client_socket.close()

if __name__ == '__main__':
    # 1 总机 - 创建TCP套接字<服务器套接字 监听套接字>
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 可以立即重用 套接字资源(IP和端口)  而不需要等待2MSL时间
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2 固定号码 - 固定端口
    server_socket.bind(('', 10088))

    # 3 安装客户服务系统 -  主动-> 被动监听模式
    server_socket.listen(128)

    while True:
        # 4 从等待服务区取出一个客户端用以服务 转接到分机 - 接受连接
        # (和客户端关联起来的套接字对象<socket.socket>, 客户端套接字地址('192.168.33.110', 46080))
        client_socket, client_address = server_socket.accept()
        print("接受到了来自%s的连接请求" % str(client_address))
        # client_request(client_socket)
        thd = threading.Thread(target=client_request, args=(client_socket,))
        thd.start()

    # 7 总机挂机
    server_socket.close()
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-12-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 全栈技术精选 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1线程
    • 1.1 多任务
      • 1.2 线程概念
        • 1.3 创建线程
          • 1.4 多线程执行的顺序是无序的 随机的
            • 1.5 主线程执行完成后不会立即退出而是等待子线程一起退出
              • 1.6 自定义线程类
                • 1.7 daemon线程
                  • 1.8 资源竞争问题
                    • 1.9 多线程同步
                      • 1.10 互斥锁
                        • 1.11 死锁问题
                          • 1.12 多任务UDP聊天工具
                            • 1.13 TCP服务器轮流为多个客户端服务
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档