Python 线程

Python 线程,第1张

文章目录
    • 线程介绍
    • 代码实现线程
      • threading 模块
          • threading.Thread
          • 1. 创建线程
          • 2. 线程实现TCP服务端的并发
          • 3. 线程 join 方法
          • 4. 线程之间共享
          • 5. 线程对象属性和方法
          • 守护线程
      • GIL全局解释器锁

线程介绍
1. 在前面了解了进程的概念,简单来说进程就是在内存中申请了一块内存空间,其实还有一个线程的概念,
线程包含在进程之中,是代码真正的执行者。也就是说进程其实是一个资源单位,而线程是执行单位。
2. 线程是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以存在多个
线程,每条线程并行执行不同的任务。可以想象成进程是一个车间,线程就是车间里的流水线。
为什么还要划分线程?

因为开设线程的消耗远远小于进程

  	开进程的流程:
    	1.申请内存空间
      	2.拷贝代码
   	
   	而开线程,无需申请内存空间,无需拷贝代码
   	

线程还可以分为两类:用户级线程(User-Level Thread)和内核级线程(Kernel-Level Thread),后者又
称为内核支持的线程或轻量级进程。在多线程 *** 作系统中,各个系统的实现方式并不相同,在有的系统中实现
了用户级线程,有的系统中实现了内核级线程。

用户级线程:内核的切换由用户态程序自己控制内核切换,不需要内核干涉,少了进出内核态的消耗,但不能很
好的利用多核Cpu

内核级线程:切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户
态;可以很好的利用smp,即利用多核cpu。类似于 windows线程。

代码实现线程

线程的 *** 作代码和进程类似,需要使用的是 threading 模块该模块相当于 multiprocessing 的作用,

threading 模块 threading.Thread

*** 作线程需要使用的是 threading 模块下的 Thread ,使用的方法和 Process 类似。

1. 创建线程
创建线程的方式也是有俩种,通过指定函数或者继承类的方式。
和创建进程不同的是,创建线程代码不需要在 __main__ 方法下。因为新线程不需要复制代码。


指定函数方式

代码示例一

		from threading import Thread
		
		def run(username): 
		    print(f'{username} is running')
		
		if __name__ == '__main__':  # 不需要在 __main__ 下写,可以保持习惯写下。
		    t = Thread(target=run, args=('XWenXiang', ))
		    t.start()
			print('主线程')

输出结果
		XWenXiang is running
		主线程


1. 同样是指定函数传入参数的形式。生成线程对象后调用 start()方法启动。
2. 但是此时发现主线程中打印的话在最后面执行了,其实是因为创建线程的开销极小,几乎是一瞬间就可以
创建,也就是执行的速度很快。

代码示例二

		from threading import Thread
		import time
		
		def run(username):
		    time.sleep(3)
		    print(f'{username} is running')
		
		if __name__ == '__main__':  # 不需要在 __main__ 下写,可以保持习惯写下。
		    t = Thread(target=run, args=('XWenXiang',))
		    t.start()
		    print('主线程')


1. 我们可以使用 time 模块增加子线程的运行时间,这样主线程的代码会被优先执行。

'''
	虽然主线程的代码先执行完了,但是并不会完全结束,因为主线程结束也就标志着整个进程的结束,要确
保子线程运行过程中所需的各项资源
'''

继承类方法

代码示例

		from threading import Thread
		
		class MyThread(Thread):
		    def __init__(self, name):  
		        super().__init__()
		        self.name = name
		    def run(self):
		        print(f'{self.name} is running')
		
		if __name__ == '__main__':  # 可以不写。
		    t = MyThread('XWenXiang')
		    t.start()

输出结果
		XWenXiang is running

1. 同样的继承 Thread类,并定义 run() 方法,该方法也会自动被 target 指定执行。并通过__init__
来传参。

2. 线程实现TCP服务端的并发

我们可以用多线程简单模拟一下并发 *** 作

服务端代码

		from threading import Thread
		import socket
		
		# 如果是进程的方式这些代码要放在 __main__ 下
		s = socket.socket()  
		s.bind(('127.0.0.1', 8888))
		s.listen(5)
		
		def run(sock):
		    while True:
		        res = sock.recv(1024)
		        print(res.decode('utf8'))
		        sock.send('我是服务端'.encode('utf8'))
		
		while True:
		    sock, addr = s.accept()
		    t = Thread(target=run, args=(sock,))
		    t.start()


客户端代码

		import socket
		
		c = socket.socket()
		c.connect(('127.0.0.1', 8888))
		
		while True:
		    info = input('>>>  ').strip()
		    c.send(info.encode('utf8'))
		    res = c.recv(1024)
		    print(res.decode('utf8'))


3. 线程 join 方法

前面只不过是因为子线程比较快才会先执行,其实主线程和子线程是异步的,可以通过 time 模块的time.sleep 方法来更好的看到。那么将异步改成同步也是使用 join 方法,它可以让主线程等待子线程结束。

代码示例

		from threading import Thread
		import time
		
		def run(username):
		    time.sleep(3)
		    print(f'{username} is running')
		
		if __name__ == '__main__':  # 不需要在 __main__ 下写,可以保持习惯写下。
		    t = Thread(target=run, args=('XWenXiang',))
		    t.start()
		    t.join()
		    print('主线程')

输出结果
		XWenXiang is running
		主线程


1. 此时主线程会等待子线程结束后在执行其他代码,所以子线程的代码先打印出来。

4. 线程之间共享

进程与进程之间默认隔离,而线程与线程之间数据是共享的,因为创建了新线程也是在同一个进程里面。

代码示例

		from threading import Thread
		
		username = 'XWenXiang'
		def run():
		    global username
		    username = 'XXX'
		    print(f'{username} is running')
		
		t = Thread(target=run)
		t.start()
		t.join()
		print('主线程')
		print(username)


输出结果
		XXX is running
		主线程
		XXX


1. 此时的变量已经被修改了,证明了进程之间数据是共享的。

5. 线程对象属性和方法

验证线程是否处于一个进程

代码示例

		from threading import Thread
		import os
		
		def run():
		    print(os.getpid())  # 获取子线程所在的进程号
		
		t = Thread(target=run)
		t.start()
		t.join()
		print(os.getpid())  # 获取主进程所在的进程号


1. 通过 os 模块分别在子线程和主线程中获取进程号,发现他们是一样的,验证了他们确实是在同一个进程

统计进程下活跃的线程数
使用的是模块 threading 里的方法 active_count,需要导入

代码示例
		
		from threading import Thread, active_count
		import time
		
		def run(username):
		    time.sleep(2)
		    print(f'{username} is running')
		
		t = Thread(target=run, args=('XWenXiang',))
		t.start()
		print(active_count())
		print('主线程')


输出结果
		2
		主线程
		XWenXiang is running


1. 由于线程速度比较快,使用time延迟几秒。
2. 导入方法执行,此时统计的数量是 2 个,因为主线程也被包含在内了。

获取线程的名字
获取线程的名字可以使用方法 current_thread() ,或者用类的方式取出 self.name

代码示例(current_thread()方法)

		from threading import Thread, current_thread
		import time
		
		def run():
		    time.sleep(1)
		    print(current_thread().name)
		
		t = Thread(target=run)
		t.start()
		print('主线程', current_thread().name)

输出结果
		主线程 MainThread
		Thread-1 (run)
		

1. 主线程和子线程的名称是不一样的
代码示例(self.name)

		from threading import Thread
		
		class MyThread(Thread):
		    def run(self):
		        print(self.name)
		
		if __name__ == '__main__':  # 可以不写。
		    t = MyThread()
		    t.start()

输出结果
		Thread-1


判断线程是否存活

使用的方法是 is_alive()

代码示例

		from threading import Thread
		import time
		
		def run(name):
		    print(f'{name} is running')
		    time.sleep(2)
		    print(f'{name} is over')
		
		t = Thread(target=run, args=('XWenXiang',))
		t.start()
		print(t.is_alive())  # 判断线程是否存活
		print('主线程')

输出结果
		XWenXiang is running
		True
		主线程
		XWenXiang is over

守护线程

和守护进程相似,守护线程就是会随着主线程的结束而结束

代码示例

		from threading import Thread
		import time
		
		def run(name):
		    print(f'{name} is running')
		    time.sleep(3)
		    print(f'{name} is over')		
		
		t = Thread(target=run, args=('XWenXiang',))
		t.daemon = True
		t.start()
		print('主线程')


输出结果
		XWenXiang is running
		主线程


1. 将子线程设成守护线程后,不管子线程里的代码需要运行多少时间,都会随着主进程的结束而结束,且此
时主线程结束会将子线程进行回收。所以示例中子线程只执行了一部分。

1. 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束

2. 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。

也就是说,主线程在有非守护线程存在的情况下,设置守护线程作用不大

代码示例

		from threading import Thread
		import time
		
		def run(name):
		    print(f'{name} is running')
		    time.sleep(1)
		    print(f'{name} is over')
		def run1(name):
		    print(f'{name} is running')
		    time.sleep(2)
		    print(f'{name} is over')
		
		t = Thread(target=run, args=('XWenXiang',))
		t1 = Thread(target=run1, args=('XXX',))
		t.daemon = True
		t.start()
		t1.start()
		print('主线程')


输出结果
		XWenXiang is running
		XXX is running
		主线程
		XWenXiang is over
		XXX is over


1. 此时除了守护线程外还有一个线程,当主线程代码执行完不会马上结束,会等待子线程结束并回收。和不
设置守护线程效果一样。

GIL全局解释器锁
GIL全局解释器锁在官方文档中是这么描述的:

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly 
because CPython’s memory management is not thread-safe. (However, since the GIL exists,
other features have grown to depend on the guarantees that it enforces.)
理解:
		首先要知道 python 解释器的类别有很多,例如 Cpython、Jpython、Ppython		
		GIL只存在于CPython解释器中,GIL是一把互斥锁,用于阻止同一个进程下的多个线程同时执行,
	也就是说同一时间只执行一个线程,不能利用多核优势。这是因为CPython解释器中的垃圾回收机制不是
	线程安全的,垃圾回收机制代码在进程中也是以线程存在。
		我们可以反推一下,如果可以多个线程同时进行,有可能会出现在变量名都还没有赋值上就被垃圾
	回收机制给清除了,也就是说会产生垃圾回收机制与正常线程之间数据错乱。所以我们需要一把锁进行
	限制。
		GIL是一把全局解释器锁,因为执行代码都需要用到解释器,加上锁之后线程就需要通过抢锁来得到
	解释器资源,等到运行结束后放锁让下一个线程抢锁。所以一次只能运行一个线程。


   所有的解释型语言都无法做到同一个进程下多个线程利用多核优势
   如果你想让你的应用更好地利用多核心计算机的计算资源,
   推荐你使用 multiprocessing 或 concurrent.futures.ProcessPoolExecutor。 
   但是,如果你想要同时运行多个 I/O 密集型任务,则多线程仍然是一个合适的模型(使用多道技术)。
   

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/714200.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-24
下一篇 2022-04-24

发表评论

登录后才能评论

评论列表(0条)

保存