线程定义
在Python中,想要实现多任务还可以使用多线程来完成。
为什么使用多线程?
进程是分配资源的最小单位 , 一旦创建一个进程就会分配一定的资源 , 就像跟两个人聊QQ就需要打开两个QQ软件一样是比较浪费资源的 .
线程是程序执行的最小单位 , 实际上进程只负责分配资源 , 而利用这些资源执行程序的是线程 , 也就说进程是线程的容器 , 一个进程中最少有一个线程来负责执行程序 . 同时线程自己不拥有系统资源,只需要一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源 . 这就像通过一个QQ软件(一个进程)打开两个窗口(两个线程)跟两个人聊天一样 , 实现多任务的同时也节省了资源 .
多线程完成多任务
线程的创建步骤
-
导入线程模块
import threading
-
通过线程类创建线程对象
线程对象 = threading.Thread(target=任务名)
-
启动线程执行任务
线程对象.start()
通过线程类创建线程对象
代码演示
import time
import threading
# 编写代码
def coding():
for i in range(3):
print("coding...")
time.sleep(0.2)
# 听音乐
def music():
for i in range(3):
print("music...")
time.sleep(0.2)
if __name__ == '__main__':
# coding()
# music()
# 创建子线程
coding_thread = threading.Thread(target=coding)
music_thread = threading.Thread(target=music)
# 启动子线程执行任务
coding_thread.start()
music_thread.start()
线程执行带有参数的任务
参数名 | 说明 |
---|---|
args | 以元组的方式给执行任务传参 |
kwargs | 以字典方式给执行任务传参 |
import time
import threading
# 编写代码
def coding(num):
for i in range(num):
print("coding...")
time.sleep(0.2)
# 听音乐
def music(count):
for i in range(count):
print("music...")
time.sleep(0.2)
if __name__ == '__main__':
# coding()
# music()
# 创建子线程
coding_thread = threading.Thread(target=coding, args=(3,))
music_thread = threading.Thread(target=music, kwargs={"count" : 1})
# 启动子线程执行任务
coding_thread.start()
music_thread.start()
线程执行带有参数的任务传参有两种方式:
1.元组方式传参 :元组方式传参一定要和参数的顺序保持一致。
2.字典方式传参:字典方式传参字典中的key一定要和参数名保持一致。
主线程和子线程的结束顺序
设置守护主线程
设置守护主线程的目的是主线程退出子线程销毁,不让主线程再等待子线程去执行。
设置守护主线程有两种方式:
threading.Thread(target=work, daemon=True)
线程对象.setDaemon(True)
threading.enumerate() 获取当前所有活跃的线程对象列表。使用 len() 对列表求长度可以看到当前活跃的线程的个数
主线程可以不断判断活跃线程个数,如果个数小于等于1,说明其它线程已经执行完成
import time
import threading
# 工作函数
def work():
for i in range(10):
print("work...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子线程
# 方式1 参数方式设置守护主线程
# work_thread = threading.Thread(target=work, daemon=True)
work_thread = threading.Thread(target=work)
# 启动线程
# 方式2
work_thread.setDaemon(True)
work_thread.start()
# 延时一秒
time.sleep(1)
print("主线程执行完毕")
线程间的执行顺序
线程之间执行是无序的
由CPU调度决定某个线程先执行的
import threading
import time
# 获取线程信息函数
def get_info():
time.sleep(0.5)
# 获取线程信息
current_thread = threading.current_thread()
print(current_thread)
if __name__ == '__main__':
# 创建子线程
for i in range(10):
sub_thread = threading.Thread(target=get_info)
sub_thread.start()
import threading
# 全局变量
g_num = 0
# 对g_num进行加操作
def sum_num1():
for i in range(1000000):
global g_num
g_num += 1
print("g_num1:", g_num)
# 对g_num进行加操作
def sum_num2():
for i in range(1000000):
global g_num
g_num += 1
print("g_num2:", g_num)
if __name__ == '__main__':
# 创建子线程
sum1_thread = threading.Thread(target=sum_num1)
sum2_thread = threading.Thread(target=sum_num2)
# 启动线程
sum1_thread.start()
sum2_thread.start()
互斥锁的使用
互斥锁: 对共享数据进行锁定,保证同一时刻只有一个线程去操作。
互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程进行等待,等锁使用完释放后,其它等待的线程再去抢这个锁。
使用
-
互斥锁的创建
mutex = threading.Lock()
-
上锁
mutex.acquire()
-
释放锁
mutex.release()
代码演示
import threading
# 全局变量
g_num = 0
# 对g_num进行加操作
def sum_num1():
# 上锁
mutex.acquire()
for i in range(1000000):
global g_num
g_num += 1
# 解锁
mutex.release()
print("g_num1:", g_num)
# 对g_num进行加操作
def sum_num2():
# 上锁
mutex.acquire()
for i in range(1000000):
global g_num
g_num += 1
# 解锁
mutex.release()
print("g_num2:", g_num)
if __name__ == '__main__':
# 创建锁
mutex = threading.Lock()
# 创建子线程
sum1_thread = threading.Thread(target=sum_num1)
sum2_thread = threading.Thread(target=sum_num2)
# 启动线程
sum1_thread.start()
sum2_thread.start()
死锁
一直等待对方释放锁的情景就是死锁。
死锁的结果:
会造成应用程序的停止响应,不能再处理其它任务了。
产生死锁的原因:没有及时或者在正确的位置释放锁
注意点:
1.使用互斥锁的时候需要注意死锁的问题,要在合适的地方注意释放锁。
2.死锁一旦产生就会造成应用程序的停止响应,应用程序无法再继续往下执行了。
import threading
# 全局变量
g_num = 0
# 对g_num进行加操作
def sum_num1():
# 上锁
print("sun_num1...")
mutex.acquire()
for i in range(1000000):
global g_num
g_num += 1
print("g_num1:", g_num)
# 对g_num进行加操作
def sum_num2():
# 上锁
print("sun_num2...")
mutex.acquire()
for i in range(1000000):
global g_num
g_num += 1
print("g_num2:", g_num)
if __name__ == '__main__':
# 创建锁
mutex = threading.Lock()
# 创建子线程
sum1_thread = threading.Thread(target=sum_num1)
sum2_thread = threading.Thread(target=sum_num2)
# 启动线程
sum1_thread.start()
sum2_thread.start()
进程和线程对比
关系对比
- 线程是依附在进程里面的,没有进程就没有线程。
- 一个进程默认提供一条线程,进程可以创建多个线程。
区别对比
-
进程之间不共享全局变量
-
线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
-
创建进程的资源开销要比创建线程的资源开销要大
-
进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
-
线程不能够独立执行,必须依存在进程中
优缺点对比
1.进程—优点:可以用多核。缺点:资源开销大
2.线程—优点:资源开销小。缺点:不能使用多核
sum2_thread = threading.Thread(target=sum_num2)
# 启动线程
sum1_thread.start()
sum2_thread.start()
进程和线程对比
关系对比
- 线程是依附在进程里面的,没有进程就没有线程。
- 一个进程默认提供一条线程,进程可以创建多个线程。
区别对比
-
进程之间不共享全局变量
-
线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
-
创建进程的资源开销要比创建线程的资源开销要大
-
进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
-
线程不能够独立执行,必须依存在进程中
优缺点对比
进程—优点:可以用多核。缺点:资源开销大
线程—优点:资源开销小。缺点:不能使用多核