{"action":"opened","issue":{"url":"https://api.github.com/repos/cundi/blog/issues/27","repository_url":"https://api.github.com/repos/cundi/blog","labels_url":"https://api.github.com/repos/cundi/blog/issues/27/labels{/name}","comments_url":"https://api.github.com/repos/cundi/blog/issues/27/comments","events_url":"https://api.github.com/repos/cundi/blog/issues/27/events","html_url":"https://github.com/cundi/blog/issues/27","id":133414168,"number":27,"title":"理解Python中的线程［翻译］","user":{"login":"cundi","id":10941075,"avatar_url":"https://avatars.githubusercontent.com/u/10941075?v=3","gravatar_id":"","url":"https://api.github.com/users/cundi","html_url":"https://github.com/cundi","followers_url":"https://api.github.com/users/cundi/followers","following_url":"https://api.github.com/users/cundi/following{/other_user}","gists_url":"https://api.github.com/users/cundi/gists{/gist_id}","starred_url":"https://api.github.com/users/cundi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cundi/subscriptions","organizations_url":"https://api.github.com/users/cundi/orgs","repos_url":"https://api.github.com/users/cundi/repos","events_url":"https://api.github.com/users/cundi/events{/privacy}","received_events_url":"https://api.github.com/users/cundi/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":0,"created_at":"2016-02-13T08:34:11Z","updated_at":"2016-02-13T08:34:11Z","closed_at":null,"body":"原始链接：http://agiliq.com/blog/2013/09/understanding-threads-in-python/  \r\n\r\n# Understanding Threads in Python\r\n`By : Akshar Raaj`\r\n\r\nWe will see some examples of using threads in Python and how to avoid race conditions:  \r\n\r\nYou should run each example several times to notice that threads are unpredictable and that your results differ every time.  \r\n\r\nDisclaimer: Forget anything you heard about GIL for now, because GIL is not going to mess up with scenarios I want to show.  \r\n\r\n## Example 1:\r\nWe want to fetch five different urls:   \r\n\r\n### Single threaded way:  \r\n\r\n```python\r\ndef get_responses():\r\n    urls = ['http://www.google.com', 'http://www.amazon.com', 'http://www.ebay.com', 'http://www.alibaba.com', 'http://www.reddit.com']\r\n    start = time.time()\r\n    for url in urls:\r\n        print url\r\n        resp = urllib2.urlopen(url)\r\n        print resp.getcode()\r\n    print \"Elapsed time: %s\" % (time.time()-start)\r\n\r\nget_response()\r\n```\r\n\r\nOutput is:  \r\n\r\n```python\r\nhttp://www.google.com 200\r\nhttp://www.amazon.com 200\r\nhttp://www.ebay.com 200\r\nhttp://www.alibaba.com 200\r\nhttp://www.reddit.com 200\r\nElapsed time: 3.0814409256\r\n```\r\n\r\nExplanation:  \r\n\r\n- All the urls are fetched in sequence.\r\n- Unless the processor got response from a url, it didn't fetch the next url.\r\n- Network operations are time taking, so processor was idle during the time it was expecting the response from a url.\r\n\r\nEven in a single threaded program, there is one thread of execution. Let's call it main thread. So, last example had only one thread, i.e main thread.  \r\n\r\n### Multi threaded way:  \r\nYou need to create a class which subclasses Thread:  \r\n\r\n```python\r\nfrom threading import Thread\r\n\r\nclass GetUrlThread(Thread):\r\n    def __init__(self, url):\r\n        self.url = url \r\n        super(GetUrlThread, self).__init__()\r\n\r\n    def run(self):\r\n        resp = urllib2.urlopen(self.url)\r\n        print self.url, resp.getcode()\r\n\r\ndef get_responses():\r\n    urls = ['http://www.google.com', 'http://www.amazon.com', 'http://www.ebay.com', 'http://www.alibaba.com', 'http://www.reddit.com']\r\n    start = time.time()\r\n    threads = []\r\n    for url in urls:\r\n        t = GetUrlThread(url)\r\n        threads.append(t)\r\n        t.start()\r\n    for t in threads:\r\n        t.join()\r\n    print \"Elapsed time: %s\" % (time.time()-start)\r\n\r\nget_responses()\r\n```\r\n\r\nOutput is:  \r\n\r\n```python\r\nhttp://www.reddit.com 200\r\nhttp://www.google.com 200\r\nhttp://www.amazon.com 200\r\nhttp://www.alibaba.com 200\r\nhttp://www.ebay.com 200\r\nElapsed time: 0.689890861511\r\n```\r\n\r\nExplanation:  \r\n\r\n- Appreciate the improvement in running time of this program.  \r\n\r\n- We wrote a multi threaded program to decrease processor's idle time. While waiting for response of a particular thread's url, processor can work on some other thread and fetch the other thread's url.  \r\n\r\n- We wanted one thread to act on one url, so overridden the constructor of thread class to pass it a url.  \r\n\r\n- Execution of a thread means execution of a thread's run().  \r\n\r\n- So, whatever we want the thread to do must go in its run().  \r\n\r\n- Created one thread for each url and called start() on it. This tells the processor that it can execute the particular thread i.e the run() of thread.  \r\n\r\n- We don't want the elapsed time to be evaluated until all the threads have executed, join() comes in picture here.  \r\n\r\n- Calling join() on a thread tells the main thread to wait for this particular thread to finish before the main thread can execute the next instruction.  \r\n\r\n- We call join() on all the threads, so elapsed time will be printed only after all the threads have run.  \r\n\r\n### Few things about threads\r\n- Processor might not execute run() of a thread immediately after start().\r\n- You can't say in which order run() of different threads will be called.\r\n- For a specific thread, it's guaranteed that the statements inside run() will be executed sequentially.\r\n- It means that first the the url associated with the thread will be fetched and only then the recieved response will be printed.  \r\n\r\n## Example 2:\r\nWe will demonstrate race condition with a program and then fix it:   \r\n\r\nRead[ wikipedia example](http://en.wikipedia.org/wiki/Race_condition#Example) to understand what race condition means.  \r\n\r\n```python\r\n#define a global variable\r\nsome_var = 0\r\n\r\nclass IncrementThread(Thread):\r\n    def run(self):\r\n        #we want to read a global variable\r\n        #and then increment it\r\n        global some_var\r\n        read_value = some_var\r\n        print \"some_var in %s is %d\" % (self.name, read_value)\r\n        some_var = read_value + 1 \r\n        print \"some_var in %s after increment is %d\" % (self.name, some_var)\r\n\r\ndef use_increment_thread():\r\n    threads = []\r\n    for i in range(50):\r\n        t = IncrementThread()\r\n        threads.append(t)\r\n        t.start()\r\n    for t in threads:\r\n        t.join()\r\n    print \"After 50 modifications, some_var should have become 50\"\r\n    print \"After 50 modifications, some_var is %d\" % (some_var,)\r\n\r\nuse_increment_thread()\r\n```\r\n\r\nRun this and you will find different result every time.  \r\n\r\n###Explanation:  \r\n\r\n- There is a global variable and all the threads will modify it.\r\n- All threads should add 1 to the existing value of the variable.\r\n- There are 50 threads, so at end the value of some_var should become 50, but it doesn't.\r\n\r\n### Why some_var didn't reach 50?\r\n- At some point thread t1 read the value of some_var as 15 and then processor took the control from this thread and gave it to thread t2.\r\n- t2 also reads some_var as 15.\r\n- Both t1 and t2 reset the value of some_var to 15+1 i.e 16.\r\n- But when two threads act on some_var we expected it's value to be increased by 2.\r\n- So, we have a race condition here.\r\n- A similar race condition might have occurred few more times and so value of some_var at end remains something like 41 or 42 or anything less than 50.\r\n\r\n## Fix this race condition\r\nChange the run() of IncrementThread to:  \r\n\r\n```python\r\nfrom threading import Lock\r\nlock = Lock()\r\n\r\nclass IncrementThread(Thread):\r\n    def run(self):\r\n        #we want to read a global variable\r\n        #and then increment it\r\n        global some_var\r\n        lock.acquire()\r\n        read_value = some_var\r\n        print \"some_var in %s is %d\" % (self.name, read_value)\r\n        some_var = read_value + 1\r\n        print \"some_var in %s after increment is %d\" % (self.name, some_var)\r\n        lock.release()\r\n```\r\n\r\nYou should run `use_increment_thread` again and the result will match your expectation.  \r\n\r\n### Explanation:\r\n- Lock is used to guard against race condition.\r\n- If thread t1 has acquired the lock before performing a set of operations, no other thread can perform the same set of operation until t1 releases the lock.\r\n- We want to make sure that once t1 has read some_var, no other thread can read some_var until t1 is done with modifying the value of some_var.\r\n- So reading some_var and modifying it are logically related operations here.\r\n- And that is why we keep read and modify part of some_var guarded by a Lock instance.\r\n- Lock is a separate object and it will be acquired by the thread from whose context it is called.\r\n\r\n## Example 3\r\nIn last example we saw how a global variable gets affected in multithreading. Let's see an example to verify that one thread cannot affect the instance variable of some other thread.  \r\n\r\nLet's introduce time.sleep() in this example. It will make sure that a thread goes in suspended state and hence enforces thread switching to occur.  \r\n\r\n```python\r\nimport time\r\n\r\nclass CreateListThread(Thread):\r\n    def run(self):\r\n        self.entries = []\r\n        for i in range(10):\r\n            time.sleep(1)\r\n            self.entries.append(i)\r\n        print self.entries\r\n\r\ndef use_create_list_thread():\r\n    for i in range(3):\r\n        t = CreateListThread()\r\n        t.start()\r\n\r\nuse_create_list_thread()\r\n```\r\n\r\nRun it few times and you will notice that the list do not get printed properly.  \r\n\r\nPossibly the entries of one thread was getting printed and during this operation, processor switched to some other thread and started printing the entries for other thread. We want to ensure that entries get printed one after another for separate threads.  \r\n\r\nChange run() of CreateListThread to use lock.   \r\n\r\n```python\r\nclass CreateListThread(Thread):\r\n    def run(self):\r\n        self.entries = []\r\n        for i in range(10):\r\n            time.sleep(1)\r\n            self.entries.append(i)\r\n        lock.acquire()\r\n        print self.entries\r\n        lock.release()\r\n```\r\n\r\nSo, we put the print operation inside a lock. When one thread has acquired the lock and printing its entries, no other thread can print its entries. And so you will see entries of different threads printed on separate lines.  \r\n\r\nThis will show that all threads' entries, which is an instance variable, is a list with numbers from 0 to 9. So, thread switching doesn't affect the instance variable of a thread.  \r\n"}}