python多线程基础概念

python的多线程由threading模块提供多线程的实现,python多线程的基础概念,包括GIL锁,如何线程的ID,如何启动线程,如何传入参数,如何创建后台线程, 如何使用join连接子线程并等待子线程全部结束。

1. GIL锁

python的多线程,并不是真正的多线程,因为有Global Interpreter Lock这个bug一般的全局锁存在,这使得同一时刻,只能有一个线程在执行。

需要注意的是,GIL锁并不是python语言的特性,它是实现CPython时引入的概念。一门语言的代码,可以由多种编译器来编译,比如C++的代码,你可以用GCC 来编译,也可以用Visual C++,同理,一段python代码也可以在不同的执行环境来执行,比如CPython,PyPy,JPython,这其中,JPython就没有GIL锁,由于CPython是默认的执行环境,因此,给大家造成了误会,以为python这门语言很蛋疼的弄了一个GIL锁。

由于有全局锁的存在,所以,python很难有效的利用多核,但也不是一点用处都没有了,在IO密集型的任务里,还是有用武之地的,比如你写一个多线程的爬虫,一个线程的请求发出去以后,需要等待服务器返回数据,其他的线程就可以继续执行了,充分利用网络IO。

2. 线程ID

有很多文章告诉你如何获取线程的id,方法就是threading.currentThread().ident ,但这是不对的,ident只是线程的标识,而非线程id,正确的做法是使用ctypes库获取,方法如下

import threading
import ctypes

SYS_gettid = 186
libc = ctypes.cdll.LoadLibrary('libc.so.6')
tid = libc.syscall(SYS_gettid)
print(tid)
print(threading.currentThread().ident)

上述代码要在linux环境下才能执行, 当然,你也可以将线程标识用于区分线程。

3. 启动多线程

import threading
import time

def my_print():
    for i in range(10):
        print(i)
        time.sleep(0.5)


t = threading.Thread(target=my_print)
t.start()
print('主线程结束')

你所看到的,是一个非常简单的启动多线程的方法

  1. 使用threading.Thread创建一个线程,target参数指定的是线程要执行的任务
  2. 使用start()方法启动一个线程
  3. 观察打印内容可以发现,整个进程要等到子线程t结束后才会结束

仰赖于python语言的简洁性,启动一个多线程非常的简单,程序输出结果

0
主线程结束
1
2
3
4
5
6
7
8
9

4. 继承threading.Thread

除了第3小节所展示的方法以外,还可以通过继承threading.Thread来创建一个线程

import threading
import time

def my_print():
    for i in range(10):
        print(i)
        time.sleep(0.5)


class PringThread(threading.Thread):
    def __init__(self, count):
        super().__init__()
        self.count = count

    def run(self):
        for i in range(self.count):
            print(i)
            time.sleep(0.5)


t = PringThread(10)
t.start()
print('主线程结束')

采用这种方法时,必须实现run方法

5. 传入参数

修改my_print方法

def my_print(count):
    for i in range(count):
        print(i)
        time.sleep(0.5)

通过args向线程传入参数

t = threading.Thread(target=my_print, args=(5, ))
t.start()
print('主线程结束')

args需要传入一个元组,因此,尽管只有一个参数,也要写一个逗号

6. 后台线程

import threading
import time

def my_print(count):
    for i in range(count):
        print(i)
        time.sleep(0.5)


t = threading.Thread(target=my_print, args=(5, ))
t.setDaemon(True)
t.start()
print('主线程结束')

使用setDaemon方法将线程设置为后台线程,这意味着,主线程就不会等待它结束,执行程序,输出结果为

0
主线程结束

线程启动后刚刚输出一个0,主线程就已经结束了,由于子线程是后台线程,因此输出内容不会在控制台上显示,如果你不喜欢主线程等待子线程运行的结果,那么就可以将子线程设置成后台线程

7. join

之前的示例代码中,启动线程后,立刻执行主线程里的代码,在实际应用中,通常,会使用join方法,等待子线程执行结束

import threading
import time


def my_print(n):
    for i in range(n):
        print(i)
        time.sleep(0.5)


t_lst = []
for i in range(3):
    t = threading.Thread(target=my_print,args=(5,))
    t_lst.append(t)

for t in t_lst:
    t.start()

for t in t_lst:
    t.join()

print('主线程')

三个子线程都是用join()方法,只有他们都执行结束以后才会执行print('主线程'), 等待的时间是可以设置的,比如将上面的程序改为t.join(0.1),那么主线程会每个程序都等待0.1秒钟的时间,0.3秒钟以后,主线程就不再继续等了,开始执行自己的代码,如果不设置时间就表示一直等待,直到所有子线程结束

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案