- 先看一个单线程的例子
在程序的主线程中依次执行两个任务,任务1时间为4秒,任务2时间为2秒。
由于两个任务是依次执行,从控制台输出可以看出所有任务完成总共花费了6秒。
import time;
#定义任务1
def work1():
print("任务1开始了:",time.ctime());
time.sleep(4)
print("任务1结束了:",time.ctime());
#定义任务2
def work2():
print("任务2开始了:", time.ctime());
time.sleep(2)
print("任务2结束了:", time.ctime());
#定义主程序 在主程序中依次执行两个任务
def main():
print("主函数开始了:",time.ctime());
work1();
work2();
print("主函数结束了:", time.ctime());
#执行主程序
main();
#
# 主函数开始了: Sat Feb 2 13:23:01 2019
# 任务1开始了: Sat Feb 2 13:23:01 2019
# 任务1结束了: Sat Feb 2 13:23:05 2019
# 任务2开始了: Sat Feb 2 13:23:05 2019
# 任务2结束了: Sat Feb 2 13:23:07 2019
# 主函数结束了: Sat Feb 2 13:23:07 2019
- 再将上述例子改成多线程
-在主程序中单独开启两个线程,将两个任务分别放在两个线程中去执行。这样两个任务并发执行,可以看出所有任务完成花费了4秒。因为在执行任务1时任务2也在执行,两个任务之间不需要等待。
-使用_thread包来实现多线程
import time;
import _thread;
#任务1
def work1():
print("任务1开始了:{0}\n".format(time.ctime()));
time.sleep(4)
print("任务1结束了:",time.ctime());
#任务2
def work2():
print("任务2开始了:{0}\n".format(time.ctime()));
time.sleep(2)
print("任务2结束了:", time.ctime());
#主程序
def main():
print("主函数开始了:",time.ctime());
#开启两个线程来分别执行两个任务
_thread.start_new_thread(work1,());
_thread.start_new_thread(work2,());
print("主函数结束了:", time.ctime());
#主程序等待20秒再退出,防止两个线程还没开始执行整个程序就终止了
for i in range(20):
time.sleep(1);
main();
#
# 主函数开始了: Sat Feb 2 13:36:29 2019
# 主函数结束了: Sat Feb 2 13:36:29 2019
# 任务1开始了:Sat Feb 2 13:36:29 2019
# 任务2开始了:Sat Feb 2 13:36:29 2019
# 任务2结束了: Sat Feb 2 13:36:31 2019
# 任务1结束了: Sat Feb 2 13:36:33 2019
- 使用threading模块实现多线程
-t = threading.Thread(target=xxx,args=(xxx,) //创建子线程
-t.start() //启动子线程
-t.join() //主程序等待子线程执行完毕再退出
import time;
import threading;
def work1(in1):
print("任务1开始啦:{0}\n".format(time.ctime()));
print("我是参数",in1);
time.sleep(4);
print("任务1结束了:{0}".format(time.ctime()));
def work2(in1,in2):
print("任务1开始啦:{0}\n".format(time.ctime()));
print("我是参数",in1,in2);
time.sleep(2);
print("任务2结束了:{0}".format(time.ctime()));
def main():
print("主程序开始啦:{0}".format(time.ctime()));
#创建子线程
t1 = threading.Thread(target=work1,args=("二狗子",));
t2 = threading.Thread(target=work2, args=("傻子","大笨蛋"));
#启动子线程
t1.start();
t2.start();
#等子线程执行完毕再退出
t1.join();
t2.join();
print("主程序结束了:{0}".format(time.ctime()));
main();
# 主程序开始啦:Sat Feb 2 14:26:33 2019
# 任务1开始啦:Sat Feb 2 14:26:33 2019
#
# 我是参数 二狗子
# 任务1开始啦:Sat Feb 2 14:26:33 2019
#
# 我是参数 傻子 大笨蛋
# 任务2结束了:Sat Feb 2 14:26:35 2019
# 任务1结束了:Sat Feb 2 14:26:37 2019
# 主程序结束了:Sat Feb 2 14:26:37 2019
- 守护线程与非守护线程
-当主线程执行完毕时,守护线程也会中止执行(陪葬),而非守护线程与主线程是彼此独立的,主线程结束之后非守护线程会继续执行。
-创建一个非守护线程
由输出结果可以看出当主线程结束时,子线程还在休眠,而子线程休眠过后会继续执行。
import time;
import threading;
def fun():
print("子线程开始执行");
time.sleep(4);
print("子线程执行完毕");
print("主线程开始执行");
#执行守护线程
t = threading.Thread(target=fun,args=());
t.start();
#主线程休眠两秒
time.sleep(2);
print("主线程执行结束");
主线程开始执行
子线程开始执行
主线程执行结束
子线程执行完毕
-创建一个守护线程
通过setDaemon(True)函数设置守护线程
由输出结果可以看出当主线程结束时,子线程还在休眠,但子线程休眠过后不会执行,它会随着主线程的中止而中止。
import time;
import threading;
def fun():
print("子线程开始执行");
time.sleep(4);
print("子线程执行完毕");
print("主线程开始执行");
#执行守护线程
t = threading.Thread(target=fun,args=());
t.setDaemon(True);
t.start();
#主线程休眠两秒
time.sleep(2);
print("主线程执行结束");
主线程开始执行
子线程开始执行
主线程执行结束
- 直接继承threading.Thread来实现多线程
重写threading.Thread中的run方法
子类的实例便可以直接运行
import time;
import threading;
class mythread(threading.Thread):
def __init__(self,arg):
threading.Thread.__init__(self);
self.arg = arg;
def run(self):
time.sleep(2);
print(self.arg);
for i in range(5):
t = mythread(i);
t.start();
t.join();
print("主线程执行完毕!");
#
# 0
# 1
# 2
# 3
# 4
# 主线程执行完毕!
- 线程间共享变量问题
当多个线程在同一时刻同时访问同一个变量时,就会产生不可预期的错误,这称为共享变量问题。 - 先看一个例子
定义一个全局变量,任务1是引用这个变量让它自增10次,任务2是引用这个变量让它自减十次。
让这两个任务依次执行,输出结果如预期所料,count从0增加到10,再依次减到0,最后count值还是0.
#
import threading;
count = 0;
def work1():
global count;
for i in range(0,10):
count = count + 1;
print("任务1:count={0}".format(count));
def work2():
global count;
for i in range(0,10):
count = count - 1;
print("任务2:count={0}".format(count));
work1();
work2();
#
# 任务1:count=1
# 任务1:count=2
# 任务1:count=3
# 任务1:count=4
# 任务1:count=5
# 任务1:count=6
# 任务1:count=7
# 任务1:count=8
# 任务1:count=9
# 任务1:count=10
# 任务2:count=9
# 任务2:count=8
# 任务2:count=7
# 任务2:count=6
# 任务2:count=5
# 任务2:count=4
# 任务2:count=3
# 任务2:count=2
# 任务2:count=1
# 任务2:count=0
- 把这个例子改进一下下
把上述两个任务放到两个并发的线程中去执行,看看运行结果。
从输出数据看没有发生变量冲突,两个线程交叉执行,最终结果仍然是0.
这主要是因为CPU的执行速度相对于io操作来说实在是太快了,发生变量访问冲突仍然是极小概率事件。
#
import threading;
count = 0;
def work1():
global count;
for i in range(0,10):
count = count + 1;
print("任务1:count={0}\n".format(count));
def work2():
global count;
for i in range(0,10):
count = count - 1;
print("任务2:count={0}\n".format(count));
t1 = threading.Thread(target=work1,args=());
t2 = threading.Thread(target=work2, args=());
#启动子线程
t1.start();
t2.start();
#等子线程执行完毕再退出
t1.join();
t2.join();
print("最终结果:{0}".format(count))
任务1:count=1
任务1:count=2
任务1:count=3
任务1:count=4
任务1:count=5
任务2:count=4
任务1:count=5
任务2:count=4
任务1:count=5
任务1:count=6
任务1:count=7
任务2:count=6
任务1:count=7
任务2:count=6
任务2:count=5
任务2:count=4
任务2:count=3
任务2:count=2
任务2:count=1
任务2:count=0
最终结果:0
- 这次我们把work函数中的print函数去掉,让变量count自增,自减1000000次。
这次我们再看输出结果就会发现,count的最终值几乎不会是0,而且每一次运行的结果都相差很大,这就证明了多线程共享变量的问题确实是存在的。
#
import threading;
count = 0;
def work1():
global count;
for i in range(0,1000000):
count = count + 1;
# print("任务1:count={0}\n".format(count));
def work2():
global count;
for i in range(0,1000000):
count = count - 1;
# print("任务2:count={0}\n".format(count));
t1 = threading.Thread(target=work1,args=());
t2 = threading.Thread(target=work2, args=());
#启动子线程
t1.start();
t2.start();
#等子线程执行完毕再退出
t1.join();
t2.join();
print("最终结果:{0}".format(count))
>>>
最终结果:-194244
- 通过加锁来解决共享变量问题
锁其实是一个标志,它表示一个变量正在占用一些资源。
当一个线程要访问共享资源前,先申请锁,等访问结束后再释放锁。
当一个线程申请锁后,其他线程就不能访问这个共享资源,只有等待这个线程释放锁之后才能访问。 - 利用加锁对上个例子进行改善
#
import threading;
count = 0;
#创建一个lock对象
lock = threading.Lock();
def work1():
global count;
for i in range(0,1000000):
#申请锁
lock.acquire();
count = count + 1;
#释放锁
lock.release();
# print("任务1:count={0}\n".format(count));
def work2():
global count;
for i in range(0,1000000):
#申请锁
lock.acquire();
count = count - 1;
#释放锁
lock.release();
# print("任务2:count={0}\n".format(count));
t1 = threading.Thread(target=work1,args=());
t2 = threading.Thread(target=work2, args=());
#启动子线程
t1.start();
t2.start();
#等子线程执行完毕再退出
t1.join();
t2.join();
print("最终结果:{0}".format(count))
- 死锁问题
当线程1占用了锁1,线程2占用了锁2,此时两个线程都想申请对方的锁,都在等着申请到对方的锁才肯释放自己的锁,这样两个线程就会一直盲等下去,产生死锁问题
import threading;
import time;
lock1 = threading.Lock();
lock2 = threading.Lock();
def work1():
print("-----线程1开始运行");
lock1.acquire();
print("线程1申请了锁1");
time.sleep(1);
print("线程1等待其他线程释放锁2");
lock2.acquire();
print("线程1申请了锁2");
# time.sleep(1);
lock2.release();
print("线程1释放了锁2");
lock1.release();
print("线程1释放了锁1");
def work2():
print("-----线程2开始运行");
lock2.acquire();
print("线程2申请了锁2");
time.sleep(1);
print("线程2等待其他线程释放锁1");
lock1.acquire();
print("线程2申请了锁1");
# time.sleep(2);
lock2.release();
print("线程2释放了锁1");
lock1.release();
print("线程2释放了锁2");
if __name__ == '__main__':
print("主程序启动")
t1 = threading.Thread(target=work1, args=());
t2 = threading.Thread(target=work2, args=());
t1.start();
t2.start();
t1.join();
t2.join();
print("主程序结束");
主程序启动
-----线程1开始运行
线程1申请了锁1
-----线程2开始运行
线程2申请了锁2
线程1等待其他线程释放锁2
线程2等待其他线程释放锁1
- 死锁问题的解决
最简单的办法就是,尽量不要让一个线程申请多个锁。或者设置时间限制,如果线程占用锁太长时间就中止这个线程。
也可以参照操作系统中死锁问题的解决办法。