Python学习之路(39)——理解Python中的线程

首先我们看一个单线程的例子:

import time
import urllib.request


def get_responses():
    urls = [
        'http://www.baidu.com',
        'http://www.taobao.com',
        'http://www.qq.com',
    ]
    start = time.time()
    for url in urls:
        print(url)
        resp = urllib.request.urlopen(url)
        print(resp.getcode())
    print("Elapsed time: %s" % (time.time() - start))


get_responses()

######执行结果######
C:\Python35\python3.exe D:/Project/Python/Pro_py3/test.py
http://www.baidu.com
200
http://www.taobao.com
200
http://www.qq.com
200
Elapsed time: 29.625612497329712

Process finished with exit code 0

1)urls列表中的url地址按顺序的被请求

2)直到cpu从一个url获得了response(getcode()),否则不会去请求下一个url

3)网络请求经历较长时间,所以cpu在等待网络请求回应时一直处于闲置状态

 

再看看多线程是怎么样的:

import urllib.request
import time
from threading import Thread

class GetUrlThread(Thread):
    def __init__(self, url):
        self.url = url
        super(GetUrlThread, self).__init__()

    def run(self):
        resp = urllib.request.urlopen(self.url)
        print(self.url, resp.getcode())

def get_responses():
    urls = [
        'http://www.baidu.com',
        'http://www.qq.com',
        'http://www.taobao.com',
    ]
    start = time.time()
    threads = []
    for url in urls:
        t = GetUrlThread(url)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    print("Elapsed time: %s" % (time.time()-start))

get_responses()

######执行结果######
C:\Python35\python3.exe D:/Project/Python/Pro_py3/test.py
http://www.baidu.com 200
http://www.taobao.com 200
http://ww.qq.com 200
Elapsed time: 20.748767137527466

Process finished with exit code 0

可以看出,输出结果中的url顺序不一定和urls列表中的顺序一致,且程序执行时间比单线程短。

1)多线程程序,在等待一个线程内的网络请求返回时,cpu可以切换到其他线程去执行其他线程内的网络请求处理

2)由于期望一个线程处理一个url,所以实例化线程类的时候传递了一个url

3)线程运行,即执行类的run()方法

4)为每个url创建一个线程并调用start()方法,告诉cpu可以执行线程中的run()方法

5)调用join()方法,使得在所有的线程执行完毕后再计算花费的时间

6)join()方法可以透支主线程等待这个线程结束周,才可以执行下一条指令

关于线程:

1)cpu可能不会再调用start()后马上执行run()方法

2)无法确定run()在不同线程之间的执行顺序

3)对于单独的一个线程,可以保证run()方法里的语句时按照顺序执行的

 

再看一个多线程间的资源竞争问题:

from threading import Thread

some_var = 0

class IncrementThread(Thread):
    def run(self):
        global some_var
        read_value = some_var
        print("some_var in %s is %d" % (self.name, read_value))
        some_var = read_value + 1
        print("some_var in %s after increment is %d" % (self.name, some_var))


def use_increment_thread():
    threads = []
    for i in range(50):
        t = IncrementThread()
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    print("After 50 modifications, some_var should have become 50")
    print("After 50 modifications, some_var is %d" % (some_var,))


use_increment_thread()

  

尝试多次运行这个程序代码,会出现多种不同的结果。为什么会这样呢?

1)有一个全局变量,所有线程都想修改这个变量。

2)所有的线程应该在这个全局变量上加1.

3)有50个线程,最后这个数据应该变成50,但是它却没有。为什么?

因为:

1)假设some_var为10的时候,线程t1读取了该值,而此时CPU又将控制权交给了线程t2;

2)线程t2读取的some_var值也是10;

3)两个线程t1和t2同时对some_var值进行加1操作,使其变成11;

以上就产生了资源竞争,与我们期望的t1、t2两个线程应该是some_var加2的结果出现了偏差;而相似的情况在整个程序的运行过程中也可能发生在其他的线程之间,就会出现最后的结果小于预期的50的结果。

 

解决方案(增加Lock锁实例):

from threading import Lock, Thread
lock = Lock()
some_var = 0 
 
class IncrementThread(Thread):
    def run(self):
        global some_var
        lock.acquire()
        read_value = some_var
        print("some_var in %s is %d" % (self.name, read_value))
        some_var = read_value + 1 
        print("some_var in %s after increment is %d" % (self.name, some_var))
        lock.release()
 
def use_increment_thread():
    threads = []
    for i in range(50):
        t = IncrementThread()
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    print("After 50 modifications, some_var should have become 50")
    print("After 50 modifications, some_var is %d" % (some_var,))
 
use_increment_thread()

1)引入Lock锁实例来防止竞争条件;

2)加入在执行一些操作之前,线程t1获得了锁,其他线程在t1释放Lock之前,不会执行相同的操作;

3)以确保一旦t1已经读取了some_var,直到t1完成对其的加1修改后,其他线程才可以去读取some_var值。

以上的这样一个读取和修改some_var就成为了一个逻辑上的原子操作。

 

最后我们看一个例子,一个线程中是否可以影响到其他线程内的变量(非全局变量)。

其中我们使用time模块的sleep()方法使一个线程被挂起,以强制使线程之间发生切换。

from threading import Thread, Lock
import time
 
lock = Lock()
 
class CreateListThread(Thread):
    def run(self):
        self.entries = []
        for i in range(10):
            time.sleep(1)
            self.entries.append(i)
        lock.acquire()
        print(self.entries)
        lock.release()
 
def use_create_list_thread():
    for i in range(3):
        t = CreateListThread()
        t.start()
 
use_create_list_thread()

同样,在print(self.entries)这个语句前后加上lock.acquire()获取锁和lock.release()释放锁,确保该语句是一个逻辑上的原子操作。

从运行结果可以看出,一个线程是不可以修改其他线程内的变量(非全局变量)。

posted on 2018-03-19 21:24  nicolas_Z  阅读(137)  评论(0)    收藏  举报

导航