python多线程

一. 线程:

线程是进程中执行代码的一个分支,每个执行分支(线程)要想工作执行代码需要cpu进行调度 ,也就是说线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。

二. 线程的使用:

1. 导入线程模块

#导入线程模块
import threading

2. 线程类Thread参数说明

Thread([group [, target [, name [, args [, kwargs]]]]])

  • group: 线程组,目前只能使用None
  • target: 执行的目标任务名
  • args: 以元组的方式给执行任务传参
  • kwargs: 以字典方式给执行任务传参
  • name: 线程名,一般不用设置

3. 启动线程

启动线程使用start方法

import threading
import time

def sing():
    # 获取当前线程
    for i in range(3):
        print("唱歌。。。")
        time.sleep(0.2)

def dance():
    # 获取当前线程
    for i in range(3):
        print("跳舞。。。")
        time.sleep(0.2)

if __name__ == '__main__':
    singthread = threading.Thread(target=sing)
    dancethread = threading.Thread(target=dance)

    singthread.start()
    dancethread.start()

执行结果:

唱歌。。。
跳舞。。。
跳舞。。。
唱歌。。。
唱歌。。。
跳舞。。。

Process finished with exit code 0

4. 创建子线程时参数传递    # 跟子进程类似

4.1 args元组方式传参

import threading
import time


# 带有参数的任务
def task(count):
    for i in range(count):
        print("任务执行中..", i)
        time.sleep(0.2)
    else:
        print("任务执行完成")


if __name__ == '__main__':
    # 创建子线程
    # args: 以元组的方式给任务传入参数
    sub_thread = threading.Thread(target=task, args=(5,))
    sub_thread.start()

执行结果:

任务执行中.. 0
任务执行中.. 1
任务执行中.. 2
任务执行中.. 3
任务执行中.. 4
任务执行完成

Process finished with exit code 0

4.2 kwargs字典方式传参

import threading
import time


# 带有参数的任务
def task(count):
    for i in range(count):
        print("任务执行中..", i)
        time.sleep(0.2)
    else:
        print("任务执行完成")


if __name__ == '__main__':
    # 创建子线程
    # kwargs: 表示以字典方式传入参数
    sub_thread = threading.Thread(target=task, kwargs={"count": 3})
    sub_thread.start()

执行结果:

任务执行中.. 0
任务执行中.. 1
任务执行中.. 2
任务执行完成

Process finished with exit code 0

5. 多个线程的执行时无序的

  • 线程之间执行是无序的,它是由cpu调度决定的 ,cpu调度哪个线程,哪个线程就先执行,没有调度的线程不能执行。
  • 进程之间执行也是无序的,它是由操作系统调度决定的,操作系统调度哪个进程,哪个进程就先执行,没有调度的进程不能执行。

6. 主线程会等待所有的子线程执行结束再结束

7. 设置守护线程

将子线程设置为守护主线程后,主线程结束时会销毁子线程

import threading
import time


# 测试主线程是否会等待子线程执行完成以后程序再退出
def show_info():
    for i in range(5):
        print("test:", i)
        time.sleep(0.5)


if __name__ == '__main__':
    # 创建子线程守护主线程 
    # daemon=True 守护主线程
    # 守护主线程方式1
    sub_thread = threading.Thread(target=show_info, daemon=True)
    # 设置成为守护主线程,主线程退出后子线程直接销毁不再执行子线程的代码
    # 守护主线程方式2
    # sub_thread.setDaemon(True)
    sub_thread.start()

    # 主线程延时1秒
    time.sleep(1)
    print("over")

执行结果

test: 0
test: 1
over

Process finished with exit code 0

8. 线程等待

线程对象..join()   # 等待线程执行完,再执行后续代码

8. 线程之间可以共享全局变量

import threading
import time


# 定义全局变量
my_list = list()

# 写入数据任务
def write_data():
    for i in range(5):
        my_list.append(i)
        time.sleep(0.1)
    print("write_data:", my_list)


# 读取数据任务
def read_data():
    print("read_data:", my_list)


if __name__ == '__main__':
    # 创建写入数据的线程
    write_thread = threading.Thread(target=write_data)
    # 创建读取数据的线程
    read_thread = threading.Thread(target=read_data)

    write_thread.start()
    # 主线程等待写入线程执行完成以后代码在继续往下执行
    write_thread.join()
    print("开始读取数据啦")
    read_thread.start()

执行结果:

write_data: [0, 1, 2, 3, 4]
开始读取数据啦
read_data: [0, 1, 2, 3, 4]

Process finished with exit code 0

9. 多个线程共享变量可能出现错误:

import threading

# 定义全局变量
g_num = 0


# 循环一次给全局变量加1
def sum_num1():
    for i in range(1000000):
        global g_num
        g_num += 1

    print("sum1:", g_num)


# 循环一次给全局变量加1
def sum_num2():
    for i in range(1000000):
        global g_num
        g_num += 1
    print("sum2:", g_num)


if __name__ == '__main__':
    # 创建两个线程
    first_thread = threading.Thread(target=sum_num1)
    second_thread = threading.Thread(target=sum_num2)

    # 启动线程
    first_thread.start()
    # 启动线程
    second_thread.start()

执行结果:

sum1: 1181587
sum2: 1353542

Process finished with exit code 0

10. 线程锁:

互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作。

  • 互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。

10.1 创建锁对象

# 创建锁
mutex = threading.Lock()

10.2 上锁

# 上锁
mutex.acquire()

 

10.3 释放锁

# 释放锁
mutex.release()

10.4 解决9中的问题:

import threading

# 定义全局变量
g_num = 0

# 创建锁
mutex = threading.Lock()

# 循环一次给全局变量加1
def sum_num1():
    # 上锁
    mutex.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1
    # 释放锁
    mutex.release()
    print("sum1:", g_num)


# 循环一次给全局变量加1
def sum_num2():
    mutex.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1
    mutex.release()
    print("sum2:", g_num)


if __name__ == '__main__':
    # 创建两个线程
    first_thread = threading.Thread(target=sum_num1)
    second_thread = threading.Thread(target=sum_num2)

    # 启动线程
    first_thread.start()
    # 启动线程
    second_thread.start()

执行结果:

sum1: 1000000
sum2: 2000000

Process finished with exit code 0

11. 死锁

死锁: 一直等待对方释放锁的情景就是死锁

import threading

# 创建锁
mutex = threading.Lock()

# 循环一次给全局变量加1
def task(index):
    mutex.acquire()
    my_list = [1,2,3]
    if index >= len(my_list):
        print("指针越界")
        # mutex.release()
        return
    print(my_list[i])
    mutex.release()


if __name__ == '__main__':
    for i in range(10):
        # 创建线程
        task_thread = threading.Thread(target=task, args=(i,))
        # 启动线程
        task_thread.start()

执行结果:

1
2
3
指针越界

说明:程序出现死锁

12. 线程和进程

  1. 线程是依附在进程里面的,没有进程就没有线程。
  2. 一个进程默认提供一条线程,进程可以创建多个线程。

区别对比

  1. 进程之间不共享全局变量

  2. 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步

  3. 创建进程的资源开销要比创建线程的资源开销要大

  4. 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位

  5. 线程不能够独立执行,必须依存在进程中

  6. 多进程开发比单进程多线程开发稳定性要强

优缺点对比

  • 进程优缺点:
    • 优点:可以用多核
    • 缺点:资源开销大
  • 线程优缺点:
    • 优点:资源开销小
    • 缺点:不能使用多核
posted @ 2021-01-03 19:10  foreast  阅读(149)  评论(0编辑  收藏  举报