python多线程

本文详细介绍了Python的多线程概念,包括进程与线程的区别、多线程的优势以及如何实现线程同步。通过join方法实现线程阻塞,通过Lock实现线程间的资源互斥,并探讨了Python的GIL锁带来的多线程执行限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

python多线程

线程和进程:

什么是进程?

进程:有时被称为重量级级进程,是程序的一次执行。每个进程都有自己的地址空间、内存、数据栈以及记录运行轨迹的辅助数据,操作系统管理运行的所有进程,并为这些进程公平分配时间。进程可以通过fork和spawn操作完成其他任务。因为各个进程有自己的内存空间、数据栈等,所有只能使用进程间通信(IPC),而不能直接共享信息。

什么是线程 ?

线程:有时被称为轻量级进程,是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

多线程和多进程:

对于“多任务”这个词相信大家都不会第一次看见,现在的操作系统(如:Mac OS X、UNIX、Linux、Windows等)都支持“多任务”操作系统。
什么叫“多任务”呢?简单地说就是操作系统可以同时运行多个任务。比如,一边用浏览器上网,一边听音乐,一边聊天,这就是多任务。
对于操作系统来说,一个任务就是一个进程,开启多个任务就是多进程。有些进程不止可以同时做一件事,比如Word可以同时打字、拼写检查、打印等。在一个进程内部同时要做多件事情,就需要同时运行多个线程。多线程类似于同时运行多个不同的程序,多线程的好处有以下几点:

  1. 使用线程可以把占据长时间的程序中的任务放到后台去处理。
  2. 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
  3. 程序的运行速度可能加快
  4. 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。

多线程:

Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。再加上_thread不支持守护线程,当主线程退出时,子线程无论是否在工作,都会被强制退出。而threading带守护线程,绝大多数情况下,我们只需要使用threading这个高级模块。

import threading
def loop():
      print('thread %s is running...' % threading.current_thread().name)
      for i in range(5):
           print('thread %s >>> %s' % (threading.current_thread().name, i))
print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop)
t.start()
t.join()

单线程:

from time import ctime,sleep
class Demo:
    def music(self,music):
        for i in range(3):
            print("I was listening to %s. %s" %(music,ctime()))
            sleep(1)
    def movie(self,movie):
        for i in range(3):
            print("I was at the %s! %s" %(movie,ctime()))
            sleep(1)
#单线程
if __name__ == '__main__':
    d = Demo()
    d.music('演员')
    d.movie('战狼2')
    print("all over %s" %ctime())

多线程:

import threading
...............
if __name__ == '__main__':
    threads = []
    d = Demo()
    t1 = threading.Thread(target=d.movie,args=('魔兽世界',))
    threads.append(t1)
    t2 = threading.Thread(target=d.music,args=('演员',))
    threads.append(t2)
    for i in range(len(threads)):
        threads[i].start()

print("all over %s" %ctime())

线程阻塞(join)和线程守护(setDaemon(True))

当一个进程启动之后,会默认产生一个主线程,因为线程是程序执行流的最小单元,当设置多线程时,主线程会创建多个子线程,在python中,默认情况下(其实就是setDaemon(False)),主线程执行完自己的任务以后,就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束
Python多线程的默认情况如下:

import threading,time
def run():
    time.sleep(2)
    print('当前线程的名字是: ', threading.current_thread().name)
    time.sleep(2)
if __name__ == '__main__':
    start_time = time.time()
    print('这是主线程:', threading.current_thread().name)
    thread_list = []
    for i in range(5):
        t = threading.Thread(target=run)
        thread_list.append(t)
    for t in thread_list:
        t.start()
    print('主线程结束!' , threading.current_thread().name)
    print('一共用时:', time.time()-start_time)

关键点:

  1. 我们的计时是对主线程计时,主线程结束,计时随之结束,打印出主线程的用时。
  2. 主线程的任务完成之后,主线程随之结束,子线程继续执行自己的任务,直到全部的子线程的任务全部结束,程序结束。

当我们使用setDaemon(True)方法,设置子线程为守护线程时,主线程一旦执行结束,则全部线程全部被终止执行,可能出现的情况就是,子线程的任务还没有完全执行结束,就被迫停止,例子见下面
设置守护线程:

import threading,time
def run():
    time.sleep(2)
    print('当前线程的名字是: ', threading.current_thread().name)
    time.sleep(2)
if __name__ == '__main__':
    start_time = time.time()
    print('这是主线程:', threading.current_thread().name)
    thread_list = []
    for i in range(5):
        t = threading.Thread(target=run)
        thread_list.append(t)
    for t in thread_list:
        t.setDaemon(True)
        t.start()
    print('主线程结束了!' , threading.current_thread().name)
    print('一共用时:', time.time()-start_time)

关键点:

非常明显的看到,主线程结束以后,子线程还没有来得及执行,整个程序就退出了。

此时join的作用就凸显出来了,join所完成的工作就是线程同步,即主线程任务结束之后,进入阻塞状态,一直等待其他的子线程执行结束之后,主线程在终止,例子见下面。

join的作用:

import threading,time
def run():
    time.sleep(2)
    print('当前线程的名字是: ', threading.current_thread().name)
    time.sleep(2)
if __name__ == '__main__':
    start_time = time.time()
    print('这是主线程:', threading.current_thread().name)
    thread_list = []
    for i in range(5):
        t = threading.Thread(target=run)
        thread_list.append(t)
    for t in thread_list:
        t.setDaemon(True)
        t.start()
    for t in thread_list:
        t.join()
    print('主线程结束了!' , threading.current_thread().name)
    print('一共用时:', time.time()-start_time)

关键点:

可以看到,主线程一直等待全部的子线程结束之后,主线程自身才结束,程序退出。
join有一个timeout参数:
当设置守护线程时,含义是主线程对于子线程等待timeout的时间将会杀死该子线程,最后退出程序。所以说,如果有10个子线程,全部的等待时间就是每个timeout的累加和。简单的来说,就是给每个子线程一个timeout的时间,让他去执行,时间一到,不管任务有没有完成,直接杀死。
没有设置守护线程时,主线程将会等待timeout的累加和这样的一段时间,时间一到,主线程结束,但是并没有杀死子线程,子线程依然可以继续执行,直到子线程全部结束,程序退出。

多线程lock

如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过threading.Lock()来实现:
lock = threading.Lock()
def run_thread(n):
    for i in range(100000):
        # 先要获取锁:
        lock.acquire()
        try:
            # 放心地改吧:
            change_it(n)
        finally:
            # 改完了一定要释放锁:
            lock.release()

python多线程的小尴尬

启动与CPU核心数量相同的N个线程,在4核CPU上可以监控到CPU占用率仅有102%,也就是仅使用了一核。
但是用C、C++或Java来改写相同的死循环,直接可以把全部核心跑满,4核就跑到400%,8核就跑到800%,为什么Python不行呢?
因为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锁,互不影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值