前言
之前文章给大家介绍了网络编程相关内容和进程如何使用,感兴趣的小伙伴可以前往《这是一篇散文》查看。书接上回,今天给大家介绍一下线程,顺便给进程、线程做一个小结。
一、线程
1.1 概述
线程指的是进程的执行路径, 本身是一个执行单元,也是CPU调度的基本单位。
多线程具有如下特点:
1.多线程的执行具有随机性(无序性), 其实就是在抢CPU的过程, 谁抢到, 谁执行。
2. 默认情况下主线程会等待子线程执行结束再结束。
3. 线程之间会共享当前进程的资源。
4. 多线程环境并发操作共享资源, 有可能引发安全问题, 需要通过线程同步(加锁) 的思想来解决。
1.2 如何使用
格式如下:
1. 导包.
import threading
2. 创建线程对象.
t1 = threading.Thread(target=目标函数)
3. 启动线程.
t1.start()
示例代码如下:
# 需求:多线程模拟边写代码边听音乐
# 导包
import threading, time
# 创建写代码函数
def coding(name, num):
for i in range(1, num + 1):
print(f'{name}正在敲第{i}行代码......')
time.sleep(0.2)
# 创建听音乐函数
def music(name, count):
for i in range(1, count + 1):
print(f'{name}正在听第{i}首音乐-------------')
time.sleep(0.1)
if __name__ == '__main__':
# 创建线程对象 传参
t1 = threading.Thread(name='coding线程', target=coding, args=('文柏', 10))
t2 = threading.Thread(name='music线程', target=music, kwargs={'count': 11, 'name': '文柏'})
# 启动线程
t1.start()
t2.start()
1.3 守护线程
默认情况下主线程会等待子线程执行结束再结束,实际开发中往往需要主线程结束后,子线程也随之结束,而不是继续执行。那如何实现呢?就是将子线程设置成守护进程。简单理解为“臣随君亡”,子线程就是“殉葬的臣”。
# 导包
import threading,time
# 编写写代码函数
def coding():
for i in range(10):
print(f'coding...{i}')
time.sleep(0.3)
if __name__ == '__main__':
# 创建线程对象并设置为守护线程
th = threading.Thread(target=coding,daemon=True)
# 启动线程
th.start()
# 设置主线程(main) 执行1秒后关闭
time.sleep(1)
# 打印提示
print('主线程(main)执行结束了!')
1.4 互斥锁
多线程环境并发操作共享资源, 或者说操作同一个变量时可能出现数据结果存在偏差,结果并非你想要的。这就是引发了安全问题。需要通过线程同步(加锁) 的思想来解决。
格式如下:
创建锁:mutex = threading.Lock()
加锁:mutex.acquire()
解锁:mutex.release()
代码如下:
# 需求:两个线程对全局变量做累加操作
# 导包
import threading,time
# 定义全局变量
global_num = 0
# 创建互斥锁
mutex = threading.Lock()
# 定义函数1 对global_num累加100次
def fun1():
#加锁
mutex.acquire()
# 声明全局变量 避免警告
global global_num
for i in range(100):
# 具体的累加动作
global_num +=1
# 解锁
mutex.release() # 如果故意不解锁 可以模拟死锁的情况
# 打印累加结果
print(f'fun1计算结果是{global_num}')
# 定义函数2对全局变量累加
def fun2():
# 加锁
mutex.acquire()
# 声明全局变量
global global_num
for i in range(100):
global_num += 1
# 解锁
mutex.release()
# 累加后打印结果
print(f'fun2计算结果为{global_num}')
if __name__ == '__main__':
# 创建线程对象
f1 = threading.Thread(target=fun1)
f2 = threading.Thread(target=fun2)
# 启动线程
f1.start()
f2.start()
# 通过添加互斥锁就可以保证两个函数在处理数据时相对有序,避免发生安全问题。看官可以测试一下如果不解锁,不在适当位置加锁,使用两个不同的互斥锁会发生什么情况?
二、进程与线程的区别
2.1关系区别
线程是依赖进程的, 且1个进程至少会有1个线程。
2.2 特点区别
1.进程间数据是相互隔离的, 线程间数据是可以共享的。
2.线程间同时操作共享数据, 可能引发安全问题, 需要用到互斥锁的思路解决。
3.进程的资源开销要比 线程的资源开销大。
4.进程程序比单进程多线程程序要更加的稳定。
2.3 优缺点
进程: 可以实现多核操作, 资源开销较大。
线程: 不能使用多核, 资源开销相对较小。
总结
进程和线程中大部分概念和用法是类似的。简单来说线程是依赖进程的,进程数据隔离, 线程数据共享。进程资源开销比线程资源开销大, 所以相对更稳定。但是无论是多进程程还是多线程, 都可以实现多任务。终极目标都是充分利用CPU资源, 提高程序的执行效率。好了,以上就是本期对进程和线程的介绍。我们下期见。