1. 线程的概念
线程,可简单理解为是程序执行的一条分支,也是程序执行流的最小单元。线程是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属于一个进程的其它线程共享进程所拥有的全部资源。
1.1 主线程
当一个程序启动时,就有一个进程被操作系统创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程,简而言之:程序启动就会创建一个主线程。
主线程的重要性体现在两个方面:
(1)主线程是可以产生其它子线程的线
(2)通常它必须最后完成执行,比如执行各种关闭动作
1.2 子线程
可以看做是程序执行的一条分支,当子线程启动后会和主线程一起同时执行。
1.3 单线程与多线程基本示例代码
单线程:
import time
def sayHello():
print("Hello")
time.sleep(1)
if __name__ == '__main__':
for i in range(5):
sayHello()
结果:(耗费约5s时间)
多线程:
import time,threading
def sayHello():
print("Hello")
time.sleep(1)
if __name__ == '__main__':
for i in range(5):
# 创建子线程对象
thread_obj = threading.Thread(target=sayHello)
# 启动子线程对象
thread_obj.start()
结果:(耗费约1s时间)
1.4 主线程会等待所有的子线程结束后才结束
import time,threading
def sing():
for i in range(3):
print("is singing... %d" % i)
time.sleep(1)
def dance():
for i in range(3):
print("is dancing... %d" % i)
time.sleep(1)
if __name__ == '__main__':
print("The main thread starts to execute")
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
print("Main thread execution completed")
结果:(可以看到主线程执行完毕后,在等待所有线程执行完毕后才结束运行)
2. 线程的数量
使用threading.enumerate()可以获取当前所有活跃的线程对象列表,再使用len()查看活跃的线程数量。
import time,threading
def sing():
for i in range(3):
print("is singing... %d" % i)
time.sleep(1)
def dance():
for i in range(3):
print("is dancing... %d" % i)
time.sleep(1)
if __name__ == '__main__':
thread_list = threading.enumerate()
print("\nCurrent number of threads:%d" % len(thread_list))
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
thread_list = threading.enumerate()
print("\nCurrent number of threads:%d" % len(thread_list))
结果:
3.线程的参数
线程传递参数有三种方法:
1.使用元组传递:
threading.Thread(target=xxx, args=(参数1,参数2,....))
import time,threading
def test(a,b,c):
print("a=%d,b=%d,c=%d" % (a,b,c))
time.sleep(1)
if __name__ == '__main__':
for i in range(5):
# 创建子线程对象
thread_obj = threading.Thread(target=test, args=(10,20,30))
# 启动子线程对象
thread_obj.start()
结果:
2.使用字典传递
threading.Thread(target=xxx, kwargs={"参数名1":"参数值1", "参数名2":"参数值2",...})
import time,threading
def test(a,b,c):
print("a=%d,b=%d,c=%d" % (a,b,c))
time.sleep(1)
if __name__ == '__main__':
for i in range(5):
# 创建子线程对象
thread_obj = threading.Thread(target=test, kwargs={"a":10,"c":20,"b":30,})
# 启动子线程对象
thread_obj.start()
结果:
3.同时使用元组和字典
threading.Thread(target=xxx, args=(参数1,参数2,....), kwargs={"参数名1":"参数值1", "参数名2":"参数值2",...})
import time,threading
def test(a,b,c):
print("a=%d,b=%d,c=%d" % (a,b,c))
time.sleep(1)
if __name__ == '__main__':
for i in range(5):
# 创建子线程对象
thread_obj = threading.Thread(target=test, args=(10,), kwargs={"c":20,"b":30,})
# 启动子线程对象
thread_obj.start()
结果:
4.守护线程
如果在程序中将子线程设置为守护线程,则该子线程会在主线程结束时自动退出,设置方式为thread.setDaemon(True),要在thread.start()之前设置,默认是false的,也就是主线程结束时,子线程依然在执行。
import time,threading
def test(a,b,c):
for i in range(5):
print("正在输出:%d" % (i))
time.sleep(1)
if __name__ == '__main__':
# 创建子线程对象
thread_obj = threading.Thread(target=test, args=(10,), kwargs={"c":20,"b":30,})
# 设置线程守护:子线程守护主线程
thread_obj.setDaemon(True)
# 启动子线程对象
thread_obj.start()
time.sleep(1)
print("主线程即将结束...")
# 退出主线程
exit()
结果:
5. 并行和并发
5.1 多任务的概念
其实就是操作系统轮流让各个任务交替执行,如:任务1执行0.01秒,然后任务2执行0.01秒,再让任务3执行0.01秒...这样反复执行下去,表面上看,每个任务都是交替执行的,但是由于CPU执行速度非常快,我们就感觉所有任务都在同时执行一样。
5.2 并发和并行的概念
并发:并发指的是任务数大于CPU核数,通过操作系统的各种调度算法,实现用多个任务“一起”执行(实际总有一些任务不执行,因为切换任务的速度相当快,看上去一起执行而已)
并行:指的是任务数小于或等于CPU核数,即任务真的是一起执行的。
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以操作系统也会自动把很多任务轮流调度到每个核心上执行。
6. 自定义线程类
通过threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只需要以下三步:
1.让自定义类继承 threading.Thread
2.让自定义类重写run方法
3.通过实例化自定义类对象.start()方法启动自定义线程
import threading,time
# 自定义线程类
class MyThread(threading.Thread):
def __init__(self, num):
super().__init__() # 要先调用父类的init方法否则会报错
self.num = num
# 重写 父类run方法
def run(self):
for i in range(self.num):
print("正在执行子线程的run方法...",i)
time.sleep(0.5)
if __name__ == '__main__':
mythread = MyThread(5)
mythread.start()
7. 多线程共享全局变量
这里创建两个函数,在work1函数中1对全局变量g_num的值进行修改,与此同时在work2函数中获取g_num的值。
import threading,time
# 定义一个全局变量
g_num = 0
def work1():
# 申明g_num是一个全局变量
global g_num
for i in range(10):
g_num += 1
time.sleep(0.5)
print("work1------%d\n" % g_num)
def work2():
for i in range(10):
time.sleep(0.5)
print("work2------%d\n" % g_num)
if __name__ == '__main__':
t1 = threading.Thread(target=work1)
t2 = threading.Thread(target=work2)
t1.start()
t2.start()
# 在子线程结束后打印g_num
while len(threading.enumerate()) !=1:
time.sleep(1)
print("main------", g_num)
结果:(works1和works2一次输出1-10,只不过各有0.5时间间隔,总的有1s间隔,时间差出现下面这样显示结果)
8. 多线程共享全局变量的问题
import threading,time
# 定义一个全局变量
g_num = 0
def work1():
# 申明g_num是一个全局变量
global g_num
for i in range(1000000):
g_num += 1
print("work1------%d\n" % g_num)
def work2():
global g_num
for i in range(1000000):
g_num += 1
print("work2------%d\n" % g_num)
if __name__ == '__main__':
t1 = threading.Thread(target=work1)
t2 = threading.Thread(target=work2)
t1.start()
t2.start()
# 在子线程结束后打印g_num
while len(threading.enumerate()) !=1:
time.sleep(1)
print("main------", g_num)
结果:
解决方法:
1.通过join()方法,让t1线程优先执行,t1执行完毕后,t2才执行
这种方式的缺点是把多线程变成了单线程,影响程序执行效率
import threading,time
# 定义一个全局变量
g_num = 0
def work1():
# 申明g_num是一个全局变量
global g_num
for i in range(1000000):
g_num += 1
print("work1------%d\n" % g_num)
def work2():
global g_num
for i in range(1000000):
g_num += 1
print("work2------%d\n" % g_num)
if __name__ == '__main__':
t1 = threading.Thread(target=work1)
t2 = threading.Thread(target=work2)
t1.start()
t1.join()
t2.start()
# 在子线程结束后打印g_num
while len(threading.enumerate()) !=1:
time.sleep(1)
print("main------", g_num)
结果:
2.通过加锁解决问题
9. 同步和异步
同步:多个任务之间执行的时候要求有先后顺序,必需一个先执行完成之后,另一个才能继续执行,只有一个主线。如:你说完,我再说(同一时间只能做一件事情)
异步:多个任务之间执行没有先后顺序,可以同时运行,执行的先后顺序不会有什么影响,存在多条运行主线。如:发微信(可以不用等对方回复继续发)
10. 互斥锁
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
互斥锁为资源引入一个状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其它线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其它线程才能再次锁定该资源,互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
使用互斥锁完成两个线程对同一个全局变量各加100万次的操作:
import threading,time
# 定义一个全局变量
g_num = 0
def work1():
# 申明g_num是一个全局变量
global g_num
lock.acquire() # 加锁
for i in range(1000000):
g_num += 1
lock.release() # 解锁
print("work1------%d\n" % g_num)
def work2():
global g_num
lock.acquire()
for i in range(1000000):
g_num += 1
lock.release()
print("work2------%d\n" % g_num)
if __name__ == '__main__':
# 创建一把互斥锁
lock = threading.Lock()
t1 = threading.Thread(target=work1)
t2 = threading.Thread(target=work2)
t1.start()
t2.start()
# 在子线程结束后打印g_num
while len(threading.enumerate()) !=1:
time.sleep(1)
print("main------", g_num)
结果:
11. 死锁
在线程共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。(比如:一条流水线负责生产螺帽,一条流水线负责生产螺母,而且两条流水线都需要负责组装,就会出现螺母等螺帽,螺帽等螺母的情况,又因为各条流水线都资源上锁,对方都拿不到,因此造成死锁)
示例:
import threading
def getValue(index):
dataList = [1,3,5,7,9]
lock.acquire()
if index >= len(dataList):
print("下标越界",index)
return
print(dataList[index])
lock.release()
if __name__ == '__main__':
# 创建锁
lock = threading.Lock()
# 创建10个线程
for i in range(10):
t = threading.Thread(target=getValue, args=(i,))
t.start()
结果:(发现当下标越界后程序被阻塞了,原因是return之前没有释放锁)
解决:在return之前释放锁
import threading
def getValue(index):
dataList = [1,3,5,7,9]
lock.acquire()
if index >= len(dataList):
print("下标越界",index)
lock.release()
return
print(dataList[index])
lock.release()
if __name__ == '__main__':
# 创建锁
lock = threading.Lock()
# 创建10个线程
for i in range(10):
t = threading.Thread(target=getValue, args=(i,))
t.start()
结果: