第十篇:线程

本篇内容

  1. 开启线程的方式
  2. 线程pid
  3. 线程其他方法
  4. 守护线程
  5. GIL
  6. 互斥锁
  7. Event
  8. 线程池

 

一、 开启线程的方式

开启线程的方式分为两种:

(1)利用模块开启线程:

#!/usr/binl/env python
#encoding: utf-8
#author: YangLei

from threading import Thread

def f1():
    print("f1 is running")

if __name__ == '__main__':
    t = Thread(target=f1,)
    t.start()
    print("主")

 (2)利用类开启线程:

#!/usr/binl/env python
#encoding: utf-8
#author: YangLei

from threading import Thread

class CustomThread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        print('%s is running' % self.name)

if __name__ == '__main__':
    t = CustomThread("yanglei")
    t.start()
    print("主")

 总结:由执行过程可以看出,线程的开启速度比进程开启的速度快。

 

二、线程pid

同一个进程中,开启的线程pid相同。

#!/usr/binl/env python
#encoding: utf-8
#author: YangLei

from threading import Thread
import os

def f1():
    print("%s is running" % os.getpid())

if __name__ == '__main__':
    t1 = Thread(target=f1,)
    t2 = Thread(target=f1,)
    t1.start()
    t2.start()
    print("主", os.getpid())

由此可见,同一个进程,线程pid相同,那么我们是不是可以大胆猜测,同一个进程中的线程资源是共享的,下面一段代码就来验证我们的猜想。

#!/usr/binl/env python
#encoding: utf-8
#author: YangLei

from threading import Thread

input_info_list = []
format_list = []

def f1():
    while True:
        input_info = input(">>>: ").strip()
        input_info_list.append(input_info)

def format():
    while True:
        if input_info_list:
            data = input_info_list.pop()
            format_list.append(data.upper())

def save():
    while True:
        if format_list:
            data = format_list.pop()
            with open("save_data.txt", "a") as f:
                f.write("%s\n" % data)

if __name__ == '__main__':
    t1 = Thread(target=f1)
    t2 = Thread(target=format)
    t3 = Thread(target=save)
    t1.start()
    t2.start()
    t3.start()

 

三、线程其他方法

current_thread:

该方法的的结果可以让我们得到一个MainThread对象,而getName函数可以使我们获得该线程的线程名。

#!/usr/binl/env python
#encoding: utf-8
#author: YangLei

from threading import Thread, current_thread
import time

def f1():
    print('%s is running' % current_thread().getName())
    time.sleep(2)

if __name__ == '__main__':
    t1 = Thread(target=f1)
    t2 = Thread(target=f1)
    t3 = Thread(target=f1)
    t1.start()
    t2.start()
    t3.start()
    print(current_thread())

 

四、守护线程

守护线程跟守护进程不同,因线程开启的速度快,所以线程一定会被开启,且如果主进程运行的时间大于守护线程的时间,该守护线程也可以正常运行结束。

例如:

#!/usr/binl/env python
#encoding: utf-8
#author: YangLei

from threading import Thread
import time

def f1():
    print("f1")
    time.sleep(3)
    print("f1 is done")

def f2():
    print("f2")
    time.sleep(10)
    print("f2 is done")

if __name__ == '__main__':
    t1 = Thread(target=f1)
    t2 = Thread(target=f2)
    t1.daemon=True
    t1.start()
    t2.start()
    print('主')

 

五、GIL

1.定义:

Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:
(1) 设置GIL
(2)切换到一个线程去运行
(3)运行:
  a. 指定数量的字节码指令,或者
  b. 线程主动让出控制(可以调用time.sleep(0))
(4)把线程设置为睡眠状态
(5)解锁GIL
(6)再次重复以上所有步骤

在调用外部代码(如C/C++扩展函数)的时候,GIL 将会被锁定,直到这个函数结束为止(由于在这期间没有Python 的字节码被运行,所以不会做线程切换)。

2.设计理念:

GIL的设计简化了CPython的实现,使得对象模型,包括关键的内建类型如字典,都是隐含可以并发访问的。锁住全局解释器使得比较容易的实现对多线程的支持,但也损失了多处理器主机的并行计算能力。
但是,不论标准的,还是第三方的扩展模块,都被设计成在进行密集计算任务是,释放GIL。
还有,就是在做I/O操作时,GIL总是会被释放。对所有面向I/O 的(会调用内建的操作系统C 代码的)程序来说,GIL 会在这个I/O 调用之前被释放,以允许其它的线程在这个线程等待I/O 的时候运行。如果是纯计算的程序,没有 I/O 操作,解释器会每隔 100 次操作就释放这把锁,让别的线程有机会执行(这个次数可以通过 sys.setcheckinterval 来调整)如果某线程并未使用很多I/O 操作,它会在自己的时间片内一直占用处理器(和GIL)。也就是说,I/O 密集型的Python 程序比计算密集型的程序更能充分利用多线程环境的好处。

 

六、互斥锁

1.定义:

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

#!/usr/binl/env python
#encoding: utf-8
#author: YangLei

from threading import Thread, Lock
import time

count = 50

def work():
    global count
    mutex.acquire()
    tmp = count
    time.sleep(0.1)
    count = tmp - 1
    mutex.release()

if __name__ == '__main__':
    mutex = Lock()
    l = []
    start = time.time()
    for i in range(50):
        t = Thread(target=work)
        l.append(t)
        t.start()
    for t in l:
        t.join()
    print("run time:%s value:%s" % (time.time()-start, count))

 2.互斥锁与join的区别:

根据刚才的例子,引发了我们的猜想,就刚才的例子来说,互斥锁跟join基本上并无太大的区别,实则不然。因这段代码特殊,写的也比较短,这几行全是运算操作,如果在运算操作之前还有其他操作,那么它俩的区别就会体现出来了。互斥锁会明显比join运行的速度快。

(1)互斥锁:

#!/usr/binl/env python
#encoding: utf-8
#author: YangLei

from threading import Thread, Lock
import time

count = 50

def work():
    time.sleep(0.05)
    global count
    mutex.acquire()
    tmp = count
    time.sleep(0.1)
    count = tmp - 1
    mutex.release()

if __name__ == '__main__':
    mutex = Lock()
    l = []
    start = time.time()
    for i in range(50):
        t = Thread(target=work)
        l.append(t)
        t.start()
    for t in l:
        t.join()
    print("run time:%s value:%s" % (time.time()-start, count))

(2)join:

#!/usr/binl/env python
#encoding: utf-8
#author: YangLei

from threading import Thread
import time

count = 50

def work():
    time.sleep(0.05)
    global count
    tmp = count
    time.sleep(0.1)
    count = tmp - 1


if __name__ == '__main__':
    start = time.time()
    for i in range(50):
        t = Thread(target=work)
        t.start()
        t.join()

    print('run time:%s value:%s' %(time.time()-start, count))

 

七、Event

Python提供了Event对象用于线程间通信,它是由线程设置的信号标志,如果信号标志位为假,则线程等待直到信号被其他线程设置成真。这一点似乎和windows的event正好相反。 Event对象实现了简单的线程通信机制,它提供了设置信号,清除信号,等待等用于实现线程间的通信。

(1)设置信号

使用Event的set()方法可以设置Event对象内部的信号标志为真。Event对象提供了isSet()方法来判断其内部信号标志的状态,当使用event对象的set()方法后,isSet()方法返回真。

(2)清除信号

使用Event对象的clear()方法可以清除Event对象内部的信号标志,即将其设为假,当使用Event的clear方法后,isSet()方法返回假。

(3)等待

Event对象wait的方法只有在内部信号为真的时候才会很快的执行并完成返回。当Event对象的内部信号标志位假时,则wait方法一直等待到其为真时才返回。

#!/usr/binl/env python
#encoding: utf-8
#author: YangLei

from threading import Thread,current_thread,Event
import time

event = Event()

def conn_mysql():
    count = 1
    while not event.is_set():
        if count > 3:
            raise ConnectionError('链接失败')
        print('%s 等待第%s次链接mysql' % (current_thread().getName(), count))
        event.wait(0.5)
        count += 1
    print('%s 链接ok' % current_thread().getName())

def check_mysql():
    print('%s 正在检查mysql状态' % current_thread().getName())
    time.sleep(1)
    event.set()

if __name__ == '__main__':
    t1 = Thread(target=conn_mysql)
    t2 = Thread(target=conn_mysql)
    check = Thread(target=check_mysql)
    t1.start()
    t2.start()
    check.start()

 

八、线程池

用ThreadPoolExecutor与用ProcessPoolExecutor看起来没什么区别,只是改了一下签名而已。

不难看出,不管是使用队列还是使用进/线程池,从多进程转化到多线程是十分容易的——仅仅是修改了几个签名而已。当然内部机制完全不同,只是python的封装非常好,使我们可以不用关心这些细节,这正是python优雅之处。

#!/usr/binl/env python
#encoding: utf-8
#author: YangLei

import requests
import os,time,threading
from concurrent.futures import ThreadPoolExecutor

def get_page(url):
    print("<%s> get :%s" % (threading.current_thread().getName(), url))
    respone = requests.get(url)
    if respone.status_code == 200:
        return {"url": url, "text": respone.text}

def parse_page(obj):
    dic = obj.result()
    print("<%s> parse :%s" % (threading.current_thread().getName(), dic["url"]))
    time.sleep(0.5)
    res = "url:%s size:%s\n" % (dic["url"], len(dic["text"]))
    with open("db.txt", "a") as f:
        f.write(res)

if __name__ == '__main__':
    p = ThreadPoolExecutor(3)
    urls = [
        "http://www.baidu.com",
        "http://www.baidu.com",
        "http://www.baidu.com",
        "http://www.baidu.com",
        "http://www.baidu.com",
        "http://www.baidu.com",
        "http://www.baidu.com",
        "http://www.baidu.com",
    ]
    for url in urls:
        p.submit(get_page,url).add_done_callback(parse_page)
    p.shutdown()
    print("主进程pid:", os.getpid())

 

posted @ 2017-09-07 16:11  00豆豆00  阅读(90)  评论(0编辑  收藏  举报