Python Thread etc

Thread

创建Thread方式

1 实例化Thread类创建线程
Thread类对象属性:

  • name :线程名。

  • ident :线程标识符。

  • daemon :布尔标志,表示这个线程是否是守护线程。

Thread类的构造方法的参数如下:

Thread(self,group=None,target=None,name=None,args=(),kwargs={},*,daemon=None)

  • group :默认为None,该参数不必理会。

  • target :指定线程要调用的方法或函数。

  • name :指定线程的别名。

  • args :指定target所指向的方法或者函数要用到的位置参数。

  • kwargs :指定target所指向的方法或者函数要用到的关键字参数。

  • daemon :指定线程是否为守护线程。
    示例:

1
2
3
4
5
6
7
8
9
import threading
def text():
print('thread %s is running.' % (threading.current_thread().name)
print('thread %s end.' % (threading.current_thread().name))
print('thread %s is running.' % (threading.current_thread().name))
t = threading.Thread(target=text)
t.start()
t.join()
print('thread %s end.' % (threading.current_thread().name))

运行结果:

1
2
3
4
thread MainThread is running.
thread Thread-1 is running.
thread Thread-1 end.
thread MainThread end.

<分析>

这个例子比较简单,只是简单的展示了创建线程的方法。每一个进程都有一个主线程,主线程是默认启动的,于是可以看到我们只实例化Thread类创建了一个线程,但运行结果却有两个线程。

另外可以发现每一个线程都有一个name,比方说MainThread(主线程)、Thread-1,这个name是哪来的呢?threading模块内有一个current_thread()函数,该函数总是返回当前线程的实例,那么current_thread().name不就是返回当前线程实例的name了么。另外,主线程默认的name就是MainThread,子线程如果没有在创建时指定,那么默认就是Thread-1、Thread-2…以此类推。

另外,启动一个线程的start()方法、用以表示现线程活动的run()方法、等待一个线程结束的join(timeout)方法,判断线程是否”存活”的is_alive()方法…这些方法都和进程中的这些方法完全类似,鉴于有关进程我已经在前两篇介绍过,这里就不再细说。

唯一提一点就是,进程中关于强制终止进程的terminate()方法不适用于Thread对象,唯独这一个不要在线程中去用。

最后,threading模块下除current_thread()之外的其他常用函数:

  • threading.enumerate() :返回一个包含正在运行的线程的list。
  • threading.activeCount() :返回正在运行的线程数量。等价于len(threading.enumerate())。

Thread类的一些其它方法:

  • getName() : 返回线程名。

  • setName() :设置线程名。

  • isDaemon() :判断该线程是否是守护线程,是返回True。

  • setDaemon() :设置线程的thread.daemon属性,该方法必须在线程start()之前调用。

以上方法都已经不常用,在Python的新版本中,设定name或者daemon属性可以不借助方法直接设定。

2 并发线程

进程和线程都可以并发执行,但线程由于划分尺度小,因此并发性相比于进程是要高的;另外,创建线程要更加简单,也更易于维护,由于多个线程共享进程的内存空间,这也使得线程更易于通信;除此之外,线程是Python直接支持的,这也简化了多线程的编程。

Python中的多线程也并不全是优点,事实上,Python中的多线程只是表面上的多线程,它和C++或者java中的不同,由于全局解释器锁GIL的存在,Python在一个时刻只会有一个线程在运行,这只是并发而不是并行。所以同样是多线程,java可以充分利用CPU的多核,但Python自始至终只会用到一个核,这就像单核CPU系统的多进程一样,Python的多线程是名不副实的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import threading
from time import sleep
def text(n):
for i in range(n):
print('thread %s is running >> %d' % (threading.current_thread().name,i))
if i == n-1:
print('thread %s end.' % (threading.current_thread().name))
break
sleep(2)
print('thread %s is running.' % (threading.current_thread().name))
t1 = threading.Thread(target=text,args=(4,))
t2 = threading.Thread(target=text,args=(4,))
t1.start()
t2.start()
t1.join()
t2.join()
print('thread %s end' % (threading.current_thread().name))

3.继承Thread类创建线程

通过继承Thread,重写Thread类的run()方法得到Thread类的子类,实例化子类也能创建线程。

1
2
3
4
5
6
7
8
9
10
11
12
import threading
class Another_thread(threading.Thread):
def __init__(self):
super().__init__()
def run(self):
print('thread %s is running.' % (threading.current_thread().name))
print('thread %s end.' % (threading.current_thread().name))
print('thread %s is running.' % (threading.current_thread().name))
t = Another_thread()
t.start()
t.join()
print('thread %s end.' % (threading.current_thread().name))

如上,通过重写Thread类的run()方法,就可以在接下来直接使用Thread的子类创建一个线程,当然也可以重写Thread类的init()方法,得到一个令人满意、功能完备的Thread类的子类。这种创建线程的方式更加适合面向对象编程

4. 实例化Thread,传入一个类实例

我们一般更倾向于使用上面介绍的两种方法创建线程,在第一种方法中,我们实例化Thread类,并传给它一个函数,第二种方法通过继承Thread得到子类创建线程,而接下来介绍的第三种方法,你会发现传入一个可调用的类实例同样可以创建线程。

这种创建线程的方法更加接近于面向对象的多线程编程,相比于传入函数而言具有更高的灵活性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import threading
from time import sleep
loops = [1,2,3]
class MyThread(object):
def __init__(self,func,args,name=''):
self.name = name
self.func = func
self.args = args
def __call__(self):
self.func(*self.args)
def text(x,sp):
print('thread %s is running >> %d.' % (threading.current_thread().name,x))
sleep(sp)
print('thread %s end.' % (threading.current_thread().name))
def main():
print('START!')
threads = []
for i in range(3):
t = threading.Thread(target=MyThread(text,(i,loops[i])))
threads.append(t)
for i in range(3):
threads[i].start()
for i in range(3):
threads[i].join()
print('ALL END!')
if __name__ == '__main__':
main()

START!
thread Thread-1 is running >> 0.
thread Thread-2 is running >> 1.
thread Thread-3 is running >> 2.
thread Thread-1 end.
thread Thread-2 end.
thread Thread-3 end.
ALL END!

<分析>

这种创建线程的方法比起前两种应该稍微复杂了一点,但实际上也就是相比于传入函数改为传入一个类,类完全由我们自己定制,构造方法init()可以添加任何我们需要的参数,之后注意这个特殊方法call(),它调用传入类的函数,并使用我们传入类的参数。

如果我们力求逻辑清晰地快速创建一个线程,任务要求不高,我们倾向于使用第一种创建线程的方法;如果我们需要完全面向对象的多线程编程,我们倾向于使用继承Thread类、实例化子类创建线程的方法,所以这第三种方法的地位有点尴尬,它并不被常用,这里了解一下知道就好。

ref:

Python 三种方法创建线程_python创建线程-CSDN博客

asyncio

asyncio 是 Python 中的一个异步 I/O 框架,它支持编写并发代码使用协程、多路复用 I/O 访问操作以及高级别并发原语(如事件循环、锁、条件变量等)。asyncio 使得 Python 能够高效地处理 I/O 密集型任务,如网络请求、文件 I/O 等

asyncio 的基本概念

  1. 事件循环(Event Loop):事件循环是 asyncio 的核心组件,它调度协程(coroutines)的执行。事件循环负责管理回调函数的注册和调度。

  2. 协程(Coroutines):协程是一种特殊的生成器函数,它可以暂停执行并恢复。协程在事件循环中被调度执行。

  3. FutureFuture 是一个特殊类型的对象,代表了将来某个时刻可能会得到的结果。Future 对象可以用来挂起协程的执行,直到结果可用为止。

  4. TaskTaskFuture 的子类,用于封装协程的执行。通过 asyncio.create_task()loop.create_task() 创建 Task 对象。

  5. 异步函数(Async Functions):定义以 async def 开头的函数称为异步函数。异步函数可以使用 await 关键字来等待另一个协程的完成。

asyncio 的基本用法

创建事件循环

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

async def hello_world():
print("Hello World!")

# 创建事件循环
loop = asyncio.get_event_loop()

# 运行协程
loop.run_until_complete(hello_world())

# 关闭事件循环
loop.close()

使用 asyncawait

1
2
3
4
5
6
7
8
9
10
11
12
import asyncio

async def sleep_and_print(seconds):
print(f"Sleeping for {seconds} seconds...")
await asyncio.sleep(seconds)
print("Done sleeping.")

async def main():
await sleep_and_print(1)

# 运行协程
asyncio.run(main())

创建任务

1
2