线程
线程是进程中执行代码的一个分支,每个执行分支(线程)要想工作执行代码,需要cpu进行调度,也就是说线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们常说的主线程
说明:程序启动默认会有一个主线程,程序员自己创建的线程可以称为子线程,多线程可以完成多任务
线程的使用:
1、导入线程模块
import threading
2、线程类 Thread 参数说明
Thread([group[,target[,name[,args[,kwargs]]]]])
- group: 指定线程组,目前只能使用None
- target: 执行的目标任务名
- name: 线程名,一般不设置
- args: 以元组方式给执行任务传参
- kwargs: 以字典方式给执行任务传参
3、启动线程
启动线程使用start方法
4、多线程完成多任务的代码
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__":
thread_sing = threading.Thread(target=sing)
thread_dance = threading.Thread(target=dance)
thread_sing.start()
thread_dance.start()
运行结果:
正在唱歌..
正在跳舞..
正在唱歌..
正在跳舞..
正在跳舞..
正在唱歌..
线程带有参数的使用同进程相同参考上篇文章
5、线程的注意点介绍
- 线程之间执行是无序的
- 主线程会等待所有的子线程执行结束后再结束
- 线程之间共享全局变量
- 线程之间共享全局变量数据出现错误问题
5.1 验证线程无序的代码:
说明:它是由cpu 调度决定的,cpu 调度哪个线程,哪个线程就先执行,进程也是无序的, 它是由操作系统调度决定的
import threading
import time
def task():
time.sleep(2)
# 获取当前线程名称
print(threading.current_thread().name)
if __name__ == "__main__":
my_list = []
for i in range(10):
sub_thread = threading.Thread(target=task)
my_list.append(sub_thread)
# for 循环是很快的几乎等于同时启动
for i in my_list:
i.start()
执行结果:
Thread-3
Thread-1
Thread-2
Thread-9
Thread-10
Thread-8
Thread-7
Thread-6
Thread-5
Thread-4
**5.2 默认情况下,**主线程会等待所有的子线程执行完成后再退出,解决办法:
- 守护主线程: sub_thread.setDaemon(True)
5.3 线程之间共享全局变量
import threading
import time
g_list =[]
def add_data():
for i in range(4):
g_list.append(i)
print("add_data:",i)
print("添加数据完成",g_list)
def read_data():
print("读取数据:",g_list)
if __name__ == "__main__":
thread_add = threading.Thread(target=add_data)
thread_read = threading.Thread(target=read_data)
thread_add.start()
# 线程等待,主线程等待添加数据线程执行完成以后,代码再继续往下执行
thread_add.join()
thread_read.start()
运行结果:
add_data: 0
add_data: 1
add_data: 2
add_data: 3
添加数据完成 [0, 1, 2, 3]
读取数据: [0, 1, 2, 3]
5.4 全局变量数据错误的解决办法:
- 线程等待(join)
- 互斥锁
代码示例:
import threading
import time
g_num = 0
# 每循环一次给全局变量+1
def cal_num1():
# 声明此处加上global 表示要修改全局变量的内存地址
global g_num
for i in range(10000):
g_num+=1
print("cal_num1:",g_num)
def cal_num2():
global g_num
for i in range(10000):
g_num+=1
print("cal_num2:", g_num)
if __name__ == "__main__":
first_thread = threading.Thread(target=cal_num1)
second_thread = threading.Thread(target=cal_num2)
# 启动线程执行任务
first_thread.start()
# 主线程等待第一个子线程执行完成后再执行(线程同步)
first_thread.join()
second_thread.start()
以上代码所示,对全局变量循环+1,若两个线程同时从内存中取g_num的值为1,同时赋值后结果仍旧为2,理论上应该为3,等于是少加了一次。join()函数可以解决此问题,解决方法二就是利用互斥锁,可以参考下一篇文章。
线程和进程的关系
1、关系对比:
- 线程是依附在进程里面的,没有进程就没有线程
- 一个进程默认提供一条线程,进程可以创建多个线程
2、区别对比
- 进程之间不会共享全局变量
- 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法:互斥锁或线程同步
- 创建进程的资源开销要比创建线程的资源开销大
- 进程是操作系统资源分配的基本单位,线程是cpu调度的基本单位
- 线程不能独立执行,必须依存在进程中
- 多进程开发比单进程多线程开发稳定性要强
3、优缺点对比
-
进程优缺点:
优点:可以用多核
缺点:资源开销大 -
线程优缺点:
优点:资源开销小
缺点:不能使用多核
4、小结
- 进程和线程都是完成多任务的一种方式
- 多进程要比多线程消耗的资源多, 但是多进程开发比单进程多线程开发稳定性要强,某个进程挂掉不会影响其他进程
- 多进程可以使用cpu的多核运行,多线程可以共享全局变量
- 线程不能单独执行必须依附在进程里面