目录
python多线程第一讲:多线程入门python多线程第二讲:Lock和RLock
什么是多线程
线程是程序执行流的最小单元,比线程更大的是进程。
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
进程在计算机中的存在形式通常是一个可见的软件,比如说QQ,而线程在QQ这个进程中的实际应用就是,QQ中会有多个线程来实现QQ的各类功能,比如在聊天的时候还能够收到邮件。
举例来说,有单核CPU
的计算机,有三个线程A、B、C,分别代表聊天、收邮件和听歌功能他们的运行是并行的,并行即多个线程争夺cpu执行权,谁争夺到谁就执行。
什么是多线程编程
指的是线程间通过cpu调度实现线程交替运行的一种编程模式
多线程编程中的基本概念
并发、并行
并发就像一个人(CPU)喂两个小孩(程序)吃饭,表面上是两个小孩在吃饭,实际是一个人在喂。
并行就是两个人喂两个小孩子吃饭。
并发和并行区别如下图:
##### 线程调度规律
CPU调度的基本单位是线程
在CPU比较繁忙资源不足的时候,操作系统只为一个含有多线程的进程分配仅有的CPU资源,这些线程就会自己争夺时间片。这就是多线程实现并发,线程之间争夺CPU资源获得执行机会。
在CPU资源较足的时候,一个进程内的多线程,可以被分配到不同的CPU资源,这就是多线程实现并行。
python中的GIL
GIL的意思是全局解释性锁,python中的线程对应C语言中的一个线程,GIL使得同一时刻只能有一个python程序运行在cpu上,一个python程序由1或多个线程组成,所以Python的线程只能并发不能并行。
线程基本使用
total = 0
def add():
global total
for i in range(1000000):
total += 1
def desc():
global total
for i in range(1000000):
total -= 1
import threading
# 第一个参数target传入一个函数,指示将哪个函数抛到线程中运行
thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=desc)
# 开启一个线程
thread1.start()
thread2.start()
# 在主线程调用 子线程.join() 函数的作用是:阻塞主线程直到子线程执行完成
thread1.join()
thread2.join()
print(total)
上面的代码都运行完了之后打印total但是结果不是0。这是因为线程安全问题导致的。如果像下面这样调用就不会有问题:
total = 0
def add():
global total
for i in range(1000000):
total += 1
def desc():
global total
for i in range(1000000):
total -= 1
add()
desc()
print(total)
线程安全
线程安全指的是一段代码,在多线程或者单线程情况下执行的结果都是一致的。
线程安全函数:
符合线程安全定义的函数称为线程安全函数
线程安全类:
一个类的所有对外开放的函数都是线程安全函数,则说这个类是线程安全类
线程安全问题怎么产生的
这里回到我们上文的那段代码
total = 0
def add():
global total
for i in range(1000000):
total += 1
def desc():
global total
for i in range(1000000):
total -= 1
import threading
# 第一个参数target指示将哪个函数抛到线程中运行
thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=desc)
# 开启一个线程
thread1.start()
thread2.start()
# 在主线程调用 子线程.join() 函数的作用是 阻塞主线程直到子线程执行完成
thread1.join()
thread2.join()
print(total)
这个函数执行结果在单线程下是0,多线程下不是0。这就说明这两个函数不是线程安全的,那是什么导致的呢——共享变量。
两个函数都涉及到对共享total的操作,分别执行了 total += 1
和 total -= 1
以第一个举例:
编译器在运行total += 1的时候其实不是做了一件事情,而是做了四件事情
1、LOAD total原来的值
2、LOAD 常量1的值
3、计算 total+1的结果
4、将 total+1的结果赋值给total
编译器在运行total -= 1的时候其实不是做了一件事情,而是做了四件事情
5、加载total原来的值
6、加载常量1的值
7、计算total-1的结果
8、将total+1的结果赋值给total
多线程情况下
1234 5678这种没问题
1 5678 234就会有问题
之所以出现这种问题是因为信息不对称。解决线程安全问题就是要使信息变得对称。这就要用到线程同步。
这个我下篇文章讲。