您现在的位置: 万盛学电脑网 >> 程序编程 >> 网络编程 >> 编程语言综合 >> 正文

在Python下尝试多线程编程

作者:佚名    责任编辑:admin    更新时间:2022-06-22

   这篇文章主要介绍了在Python下多线程编程的尝试,由于GIL的存在,多线程在Python开发领域一直是个热门问题,需要的朋友可以参考下

  多任务可以由多进程完成,也可以由一个进程内的多线程完成。

  我们前面提到了进程是由若干线程组成的,一个进程至少有一个线程。

  由于线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程。

  Python的标准库提供了两个模块:thread和threading,thread是低级模块,threading是高级模块,对thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

  启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:

  ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import time, threading   # 新线程执行的代码: def loop(): print 'thread %s is running...' % threading.current_thread().name n = 0 while n < 5: n = n + 1 print 'thread %s >>> %s' % (threading.current_thread().name, n) time.sleep(1) print 'thread %s ended.' % threading.current_thread().name   print 'thread %s is running...' % threading.current_thread().name t = threading.Thread(target=loop, name='LoopThread') t.start() t.join() print 'thread %s ended.' % threading.current_thread().name

  执行结果如下:

  ?

1 2 3 4 5 6 7 8 9 thread MainThread is running... thread LoopThread is running... thread LoopThread >>> 1 thread LoopThread >>> 2 thread LoopThread >>> 3 thread LoopThread >>> 4 thread LoopThread >>> 5 thread LoopThread ended. thread MainThread ended.

  由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……

  Lock

  多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

  来看看多个线程同时操作一个变量怎么把内容给改乱了:

  ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import time, threading     # 假定这是你的银行存款: balance = 0   def change_it(n): # 先存后取,结果应该为0: global balance balance = balance + n balance = balance - n   def run_thread(n): for i in range(100000): change_it(n)   t1 = threading.Thread(target=run_thread, args=(5,)) t2 = threading.Thread(target=run_thread, args=(8,)) t1.start() t2.start() t1.join() t2.join() print balance

  我们定义了一个共享变量balance,初始值为0,并且启动两个线程,先存后取,理论上结果应该为0,但是,由于线程的调度是由操作系统决定的,当t1、t2交替执行时,只要循环次数足够多,balance的结果就不一定是0了。

  原因是因为高级语言的一条语句在CPU执行时是若干条语句,即使一个简单的计算:

  ?

1 balance = balance + n

  也分两步:

  计算balance + n,存入临时变量中;

  将临时变量的值赋给balance。

  也就是可以看成:

  ?

1 2 x = balance + n balance = x

  由于x是局部变量,两个线程各自都有自己的x,当代码正常执行时:

  初始值 balance = 0

  ?

1 2 3 4 5 6 7 8 9 t1: x1 = balance + 5 # x1 = 0 + 5 = 5 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 t2: x2 = balance - 8 # x2 = 8 - 8 = 0 t2: balance = x2 # balance = 0

  结果 balance = 0

  但是t1和t2是交替运行的,如果操作系统以下面的顺序执行t1、t2:

  初始值 balance = 0

  ?

1 2 3 4 5 6 7 8 9 10 11 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 - 5 # x2 = 0 - 5 = -5 t2: balance = x2 # balance = -5

  结果 balance = -5

  究其原因,是因为修改balance需要多条语句,而执行这几条语句时,线程可能中断,从而导致多个线程把同一个对象的内容改乱了。

  两个线程同时一存一取,就可能导致余额不对,你肯定不希望你的银行存款莫名其妙地变成了负数,所以,我们必须确保一个线程在修改balance的时候,别的线程一定不能改。

  如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过threading.Lock()来实现:

  ?

1 2 3 4 5 6 7 8 9 10 11 12 13 balance = 0 lock = threading.Lock()