一、创建一个多进程
启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:
1.1代码及运行结果
代码:
如以上代码所示,threading.current_thread()返回进程实例,用threading.current_thread().name返回实例名称,主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程【注意代码中tt = threading.Thread(target=loop, name='LoopThread')】。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……
运行:
二、Lock
多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
2.1多个线程同时改一个变量示例
2.1.1多个线程同时改一个变量示例代码
2.1.2多个线程同时改一个变量示例运行结果
2.1.3运行原理解剖:
原因是因为高级语言的一条语句在CPU执行时是若干条语句,即使一个简单的计算:
balance=balance+n
可以视作
1、计算balance + n,存入临时变量中;
2、将临时变量的值赋给balance。
即:
x=balance+n
balance=x
由于两个线程是交替运行 ,又修改balance需要多条语句,而执行这几条语句时,线程可能中断,从而导致多个线程把同一个对象的内容改乱了,故当循环次数足够 for i in range(1000000):
可以将运行过程视作如下步骤:
初始值 balance = 0
t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2 # balance = 8
t1: balance = x1 # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1 # balance = 0
t2: x2 = balance - 8 # x2 = 0 - 8 = -8
t2: balance = x2 # balance = -8
结果 balance = -8
2.2 防止多个线程同时改一个变量的解决方案
如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。
2.2.1三步骤实现解决方案
第一步:创建锁,通过threading.Lock()来实现
第二步:获取锁,当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。
第三部:释放锁,获得锁的线程用完后一定要用 lock.release()释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally来确保锁一定会被释放。
2.2.2上锁代码
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()
2.2.3全部代码及运行结果
代码:
运行结果:
2.2.4 上锁的解决方案的优缺点
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。