Understanding Threads in Python

29 October 2013

原文地址

你会看到一些在Python中使用线程的例子以及怎样避免竞态条件:

你需要多次执行每个例子来注意到线程是不可预测的, 你得到的结果每次都不同.

免责声明: 请暂时忘掉你听说过的关于GIL的任何东西, 因为GIL不会影响我想要展示的情况.

Example 1

我们想要获取五个不同的urls:

单线程方法

1
2
3
4
5
6
7
8
9
10
11
12
def get_responses():
    urls = ['http://www.google.com', 'http://www.amazon.com', 'http://www.ebay.com',
        'http://www.alibaba.com', 'http://www.reddit.com']
    start = time.time()
    for url in urls:
        print url
        resp = urllib2.urlopen(url)
        print resp.getcode()
    print "Elapsed time:%s" % (time.time()-start)

get_responses()

输出的结果是:

1
2
3
4
5
6
http://www.google.com 200
http://www.amazon.com 200
http://www.ebay.com 200
http://www.alibaba.com 200
http://www.reddit.com 200
Elapsed time: 3.0814409256

解释:

即使在一个单线程的程序中, 也只有一个执行线程. 我们叫它主线程. 所以, 上一个例子只有一个线程, 也就是主线程.

多线程方法

你需要创建Thread类的一个子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import time
import urllib2

from threading import Thread


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

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


def get_responses():
    urls = ['http://www.google.com', 'http://www.amazon.com', 'http://www.ebay.com',
        'http://www.alibaba.com', 'http://www.reddit.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()

输出:

1
2
3
4
5
6
http://www.reddit.com 200
http://www.google.com 200
http://www.amazon.com 200
http://www.alibaba.com 200
http://www.ebay.com 200
Elapsed time: 0.689890861511

解释:

关于线程的一些东西:

Example 2

我们会用一个程序来演示竞态条件然后修复它:

先读一下维基百科的例子来理解下竞态条件是什么意思.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from threading import Thread

#define a global variable
some_var = 0


class IncrementThread(Thread):
    def run(self):
        #we want to read a global variable
        #and then increment it
        global some_var
        read_value = some_var
        print "some_var %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()

多次运行这个程序, 你会发现你每次得到的值都不一样.

解释:

为什么some_var的值不到50?

修复这个竞态条件

把IncrementThread中的run()修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from threading import Lock

lock = Lock()


class IncrementThread(Thread):
    def run(self):
        #we want to read a global variable
        #and then increment it
        global some_var
        lock.acquire()
        read_value = some_var
        print "some_var %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()

再运行一次use_increment_thread就会得到期望的结果了.

解释:

Example 3

在上一个例子中我们看到了一个全局变量会在一个多线程中受到影响. 让我们再来看一个例子来确认一个线程不能影响其他线程中的实例变量.

这个例子中引入了time.sleep()函数. 它会确保一个线程处于暂停状态, 因此强制线程交换发生.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time
from threading import Thread


class CreateListThread(Thread):
    def run(self):
        self.entries = []
        for i in range(10):
            time.sleep(1)
            self.entries.append(i)
        print self.entries


def use_create_list_thread():
    for i in range(3):
        t = CreateListThread()
        t.start()


use_create_list_thread()

运行几次之后发现列表并没有被合适地打印出来.

可能是某个线程的entries正在打印的时候处理器切换到了其他线程并且开始打印其他线程的entries. 我们想要确保对于每个单独的线程, entries都是一个打印完了再开始下一个.

用lock来改变CreateListThread的run():

1
2
3
4
5
6
7
8
9
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()

所以, 我们把打印操作放到了一个lock里面. 当一两个线程获得锁并且在打印它的entries时, 其他的线程都不能打印它们的entries. 这样你就会看到不同线程的entries在各行被打印出来了.

这会显示所有线程的entries, 它是一个实例变量, 从0到9的一个列表. 所以线程交换不会影响某个线程的实例变量.

相关文章

标签:
  • Python
  • Threads
comments powered by Disqus