python多线程

多线程

任务能够由多进程完成,也能够由一个进程内的多线程完成。
咱们前面提到了进程是由若干线程组成的,一个进程至少有一个线程。
因为线程是操做系统直接支持的执行单元,所以,高级语言一般都内置多线程的支持,Python也不例外,而且,Python的线程是真正的Posix Thread,而不是模拟出来的线程。Python的标准库提供了threading模块。html

建立

在python中多线程的实现方法有2种。python

  1. 将要执行的方法做为参数传递给Thread的构造方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import threading
    
    def worker(args):
        print("开始子进程 {0}".format(args))
        print("结束子进程 {0}".format(args))
    
    if __name__ == '__main__':
    
        print("start main")
        t1 = threading.Thread(target=worker, args=(1,))
        t2 = threading.Thread(target=worker, args=(2,))
        t1.start()
        t2.start()
        print("end main")

输出结果多线程

1
2
3
4
5
6
start main
开始子进程 1
结束子进程 1
开始子进程 2
结束子进程 2
end main

 

  1. 从Thread继承,并重写run()方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import threading
    import time
    
    class Hello(threading.Thread):
        def __init__(self, args):
            super(Hello, self).__init__()    #注意:要显式的调用父类的初始化函数
            self.args = args
    
        def run(self):
            print("开始子进程 {0}".format(self.args))
            time.sleep(1)
            print("结束子进程 {0}".format(self.args))
    
    if __name__ == '__main__':
        a = 1
        print("start main")
        t1 = Hello(1)
        t2 = Hello(2)
        t1.start()
        t2.start()
        print("end main")

输出结果并发

1
2
3
4
5
6
start main
开始子进程 1
开始子进程 2
end main
结束子进程 1
结束子进程 2

 

模块函数

  • threading.active_count()
    返回当前存活的thread对象的数量。返回的数量和enumerate()列表长度相等。
  • threading.current_thread()
    返回当前线程对象。
  • threading.get_ident()
    返回当前线程的thread identifier。这是个非零的整数。该值无特殊含义。
  • threading.enumerate()
    以列表形式返回当前存活的全部线程对象。列表中包括守护线程和由current_thread()建立的虚拟线程对象,不包括已结束和还没开始的线程。
  • threading.main_thread()
    返回主线程对象。正常状况下,主线程由python解释器启动。

    对象方法

  • start()
    启动线程,调用run方法。同一线程对象屡次调用将会出现RuntimeError错误。
  • run()
    启动线程
  • join(timeout=None)
    等待线程对象结束。
  • is_alive()
    线程是否活着

    对象属性

  • name
    线程的名称,无特殊意义。
  • daemon
    布尔值,表示该线程是否为守护线程。设置该值必须在调用run()方法前,不然会报RuntimeError错误。假如主线程不是守护线程,该主线程建立的全部子线程该值为False。

    线程锁

    多线程和多进程最大的不一样在于,多进程中,同一个变量,各自有一份拷贝存在于每一个进程中,互不影响,而多线程中,全部变量都由全部线程共享,因此,任何一个变量均可以被任何一个线程修改,所以,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
    这种状况下经过线程锁保证修改不会发生冲突。
    锁的建立经过threading.Lock()进行,操做相似于多进程。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import threading
    import time
    
    def worker(name, lock):
        with lock:
            print("start {0}".format(name))
            time.sleep(5)
            print("end {0}".format(name))
    
    if __name__ == "__main__":
        lock = threading.Lock()
        t1 = threading.Thread(target=worker, args=("worker1", lock))
        t2 = threading.Thread(target=worker, args=("worker2", lock))
        t1.start()
        t2.start()

输出结果app

1
2
3
4
start worker1
end worker1
start worker2
end worker2

 

固然,获取锁也能够经过lock.acquire()完成,释放锁经过lock.release()完成。ide

1
2
3
4
5
6
7
8
def worker(name, lock):
    lock.acquire()
    try:
        print("start {0}".format(name))
        time.sleep(5)
        print("end {0}".format(name))
    finally:
        lock.release()

 

  • 锁的好处
    确保了某段关键代码只能由一个线程从头至尾完整地执行。
  • 坏处
    阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地降低了。
    因为能够存在多个锁,不一样的线程持有不一样的锁,并试图获取对方持有的锁时,可能会形成死锁,致使多个线程所有挂起,既不能执行,也没法结束,只能靠操做系统强制终止。

    线程共享内存

    多线程和多进程不一样之处在于多线程自己就是能够和父进程共享内存的,这也是为何其中一个线程挂掉之后,为何其余线程也会死掉的道理。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import threading
    
    l = list()
    l += range(1, 4)
    
    def worker():
        l.append("GOD")
    
    if __name__ == "__main__":
        t1 = threading.Thread(target=worker)
        t2 = threading.Thread(target=worker)
        t1.start()
        t2.start()
        print(l)

输出结果函数

1
[1, 2, 3, 'GOD', 'GOD']

 

线程池

在使用多线程处理任务时也不是线程越多越好,因为在切换线程的时候,须要切换上下文环境,依然会形成cpu的大量开销。为解决这个问题,线程池的概念被提出来了。预先建立好一个较为优化的数量的线程,让过来的任务马上可以使用,就造成了线程池。
此处介绍threapool是一个第三方模块,须要使用pip install threadpool安装。优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import threadpool

def hello(m, n, o):
    print("m = {0}  n={1}  o={2}".format(m, n, o))

if __name__ == '__main__':
    lst_vars_1 = ['1', '2', '3']
    lst_vars_2 = ['4', '5', '6']
    func_var = [(lst_vars_1, None), (lst_vars_2, None)]

    pool = threadpool.ThreadPool(2)
    requests = threadpool.makeRequests(hello, func_var)
    [pool.putRequest(req) for req in requests]
    pool.wait()

 

输出结果ui

1
2
m = 1  n=2  o=3
m = 4  n=5  o=6

 

  • Executor
    在python3.3以后,并行任务可使用Executor Objects,更多内容点击concurrent.futures — Launching parallel tasks查看官方介绍。
  • 关于python中多核CPU的说明
    Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先得到GIL锁,而后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把全部线程的执行代码都给上了锁,因此,多线程在Python中只能交替执行,即便100个线程跑在100核CPU上,也只能用到1个核。
    GIL是Python解释器设计的历史遗留问题,一般咱们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。
    因此,在Python中,可使用多线程,但不要期望能有效利用多核。若是必定要经过多线程利用多核,那只能经过C扩展来实现,不过这样就失去了Python简单易用的特色。
    Python虽然不能利用多线程实现多核任务,但能够经过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。
    更多关于threading的内容能够点击threading — Thread-based parallelism查看官方介绍。
相关文章
相关标签/搜索