进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。
线程: cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。
线程的运行是没有先后顺序的
子线程全部结束,主线程才会结束
Python会自动为线程指定一个名字
import threading
import time
def test1():
for i in range(5):
print("--------test1----%d----" %i)
time.sleep(1)
def test2():
for i in range(5):
print("--------test2----%d----" %i)
time.sleep(1)
def main():
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
t2.start()
# 查看进程数量
print(threading.enumerate())
if __name__ == '__main__':
main()
通过类继承实现,只需要继承threading.Thread
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i) #name属性中保存的是当前线程的名字
print(msg)
if __name__ == '__main__':
t = MyThread()
t.start()
python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。
global,全局变量
-
在一个函数中,对全局变量进行修改的时候,到底是否需要使用global进行说明,要看是否对全局变量的执行指向进行了修改。
-
如果修改了执行,让全局变量指向了一个新的地址,那么必须用global。如果仅仅是修改了指向的空间中的数据,此时不用必须使用global
-
多线程之间,全局变量是共享的,线程之间对全局变量的随意修改可能会引起混乱,也就是线程非安全。
# 一写一读
import threading
def test1(temp):
temp.append(33)
print("-------in test1 temp = %s---------" %str(temp))
def test2(temp):
print("-------in test2 temp = %s---------" %str(temp))
g_nums = [11, 22]
def main():
# target指定这个线程去哪执行代码
# args指定将来调用函数的时候,传递什么数据过去
t1 = threading.Thread(target=test1, args=(g_nums,))
t2 = threading.Thread(target=test2, args=(g_nums,))
t1.start()
time.sleep(1)
t2.start()
time.sleep(1)
print("-------in main g_num = %s---------" %str(g_nums))
if __name__ == '__main__':
main()
# -------in test1 temp = [11, 22, 33]---------
# -------in test2 temp = [11, 22, 33]---------
# -------in main g_num = [11, 22, 33]---------
同步
同步就是协同步调,按预定的先后次序进行运行
互斥锁
同一进程中的多线程之间是共享系统资源的,多个线程同时对一个对象进行操作,一个线程操作尚未结束,另一线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁,保证每个线程对该对象的操作都得到正确的结果。
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
import threading
g_num = 0
def test1(num):
global g_num
for i in range(num):
# 上锁,如果之前没有被上锁,那么上锁成功
# 如果之前已经被上锁了,那么此时会堵塞在这里,直到这个锁被解开位置
mutex.acquire()
g_num += 1
# 解锁
mutex.release()
print("-------in test1 num = %d---------" %g_num)
def test2(num):
global g_num
for i in range(num):
# 上锁
mutex.acquire()
g_num += 1
# 解锁
mutex.release()
print("-------in test2 num = %d---------" %g_num)
# 创建一个互斥锁,默认没有上锁
mutex = threading.Lock()
def main():
# target指定这个线程去哪执行代码
# args指定将来调用函数的时候,传递什么数据过去
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
time.sleep(1)
if __name__ == '__main__':
main()
# -------in test1 num = 1992616----------------in test2 num = 2000000---------
上锁解锁过程:
-
当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。
-
每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。
-
线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。
锁的好处:
确保某段代码能够够从头到尾的正确执行
锁的坏处:
阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁
死锁
若干子线程在系统资源竞争时,每个子线程都占有一部分资源,都在等待对方对某部分资源解除占用状态,结果是谁也不愿先解锁,互相干等着,程序无法执行下去,这就是死锁。
如何避免死锁,使用银行家算法或添加超时时间。