目录

python多线程第一讲:多线程入门python多线程第二讲:Lock和RLock

什么是多线程

线程是程序执行流的最小单元,比线程更大的是进程。
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
进程在计算机中的存在形式通常是一个可见的软件,比如说QQ,而线程在QQ这个进程中的实际应用就是,QQ中会有多个线程来实现QQ的各类功能,比如在聊天的时候还能够收到邮件。
举例来说,有单核CPU的计算机,有三个线程A、B、C,分别代表聊天、收邮件和听歌功能他们的运行是并行的,并行即多个线程争夺cpu执行权,谁争夺到谁就执行。

什么是多线程编程

指的是线程间通过cpu调度实现线程交替运行的一种编程模式

多线程编程中的基本概念
并发、并行

并发就像一个人(CPU)喂两个小孩(程序)吃饭,表面上是两个小孩在吃饭,实际是一个人在喂。

并行就是两个人喂两个小孩子吃饭。

并发和并行区别如下图:

Python中如何提高多线程任务的速率 python多线程跑满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 += 1total -= 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就会有问题

之所以出现这种问题是因为信息不对称。解决线程安全问题就是要使信息变得对称。这就要用到线程同步。
这个我下篇文章讲。