day35

开启线程的两种方式:
与开启进程方式类似也是两种方式,通过实例化和类的继承

from multiprocessing import Process
			 from threading import Thread
			 import time
			
			
			 def task(name):
			     print('%s is running'%name)
			     time.sleep(1)
			     print('%s is over'%name)
			
			
			  开启线程不需要在main下面执行代码 直接书写就可以
			  但是我们还是习惯性的将启动命令写在main下面
			  t = Thread(target=task,args=('egon',))
		      p = Process(target=task,args=('jason',))
			  p.start()
			 t.start()  # 创建线程的开销非常小 几乎是代码一执行线程就已经创建了
			 print('主')



			from threading import Thread
			import time


			class MyThead(Thread):
			    def __init__(self, name):
			        """针对刷个下划线开头双下滑线结尾(__init__)的方法 统一读成 双下init"""
			        # 重写了别人的方法 又不知道别人的方法里有啥 你就调用父类的方法
			        super().__init__()
			        self.name = name

			    def run(self):
			        print('%s is running'%self.name)
			        time.sleep(1)
			        print('egon DSB')


			if __name__ == '__main__':
			    t = MyThead('egon')
			    t.start()
    print('主')

  

多线程实现TCP并发编程,通过学习多线程实现的方式我们可以实现在不调用socketsever前提下实现完成TCP协议的多并发功能
思路:并发完成的实现,是需要一个主线程处于listen也就是从半链接池里挑选连接对象的状态,而需要其余子线程完成数据的传输
综上所述,可以通过多线程完成该工作

import socket
		 from threading import Thread
		 from multiprocessing import Process
		 server =socket.socket()  # 括号内不加参数默认就是TCP协议
		 server.bind(('127.0.0.1',8080))
		 server.listen(5)
 

		 # 将服务的代码单独封装成一个函数
		 def talk(conn):
		     # 通信循环
		     while True:
		         try:
		             data = conn.recv(1024)
		             # 针对mac linux 客户端断开链接后
		             if len(data) == 0: break
		             print(data.decode('utf-8'))
		             conn.send(data.upper())
		         except ConnectionResetError as e:
		             print(e)
		             break
		     conn.close()

		 # 链接循环
		 while True:
		     conn, addr = server.accept()  # 接客
		     # 叫其他人来服务客户
		     # t = Thread(target=talk,args=(conn,))
		     t = Process(target=talk,args=(conn,))
		     t.start()
因为客户端没有多大的变化,所以在这里就不进行多余的赘述

  

线程的join方法:主线程等待子线程结束后才结束。
因为线程的特殊性质导致在同一个进程下的多个线程之间数据是共享的。
线程的其他方法:active_count, current_thread通过这两个模块获取线程使用率略为高的方法。
active_count() 统计当前正在活跃的线程数
current_thread().name 获取线程名字
守护线程:与守护进程概念类似,当主线程结束后被设置为子线程的线程必须一起结束
ps 线程的结束顺序与进程不太相同,线程运行结束之后不会立刻结束 会等待所有其他非守护线程结束才会结束 因为主线程的结束意味着所在的进程的结束。所以在这里举一个迷惑性的小例子,大家可以猜测一下运行结果。

from threading import Thread
		import time


		def foo():
		    print(123)
		    time.sleep(1)
		    print('end123')


		def func():
		    print(456)
		    time.sleep(3)
		    print('end456')


		if __name__ == '__main__':
		    t1 = Thread(target=foo)
		    t2 = Thread(target=func)
		    t1.daemon = True
		    t1.start()
		    t2.start()
		    print('主.......')

  

线程的互斥锁:既然是对比进程那么小兄弟们一个也不能缺,所以线程也无法逃避多线程操作统一数据时会发生的尴尬情况,所以
对于线程来说也需要一把锁来保护数据的安全性

对于锁的使用调用的是同一个模块lock所以就不进行过于详细地介绍,只在这里为大家在说一次两个方法
.acquire()分配锁
.release()释放锁

接下来我们趁着互斥锁的热乎劲介绍一种新鲜玩意,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.)

首先可能大部分小伙伴看不懂,没关系我们来提取其中的关键字(标注星号),首先我们看到的CPython字样,这首先说明了python解释器
的版本不是Jpython或者其他的,其次是lock,说明了在CPython解释器中GIL是一把互斥锁,用来阻止同一个进程下的多个线程的同时执行,
最后一个because以及后面部分,说明这种锁存在的意义是因为在Cpython内存管理并不是线程安全的。

总结了以上的几句话我们可以提炼出:
1.GIL不是python的特点而是CPython解释器的特点
2.GIL是保证解释器级别的数据的安全
3.GIL会导致同一个进程下的多个线程的无法同时执行即无法利用多核优势
4.针对不同的数据还是需要加不同的锁处理
5.解释型语言的通病:同一个进程下多个线程无法利用多核优势

以下是个人总结GIL互斥锁和普通互斥锁的区别:
首先是调用,普通的互斥锁需要先声明并且配合上相应的调用方法才会产生作用,而GIL作为自带的一种机制只要有起线程就需要用到
其次是释放,当一个线程进入IO交互时GIL锁就会自动释放被其他线程调用,而普通互斥锁需要手动释放

所以从以上看来单纯的使用某一种所都是不高明的选择,于是两者结合编程我们的首选。

接下来接近了尾声,我们来谈谈多线程问题,对于现在的大部分计算机(多核)来说,同一个进程下的多线程无法展现多核优势,因此多进程似乎
比较更划算,那么我们来探讨一下多线程的必要性。

多线程是否有用要看具体情况
单核:四个任务(IO密集型计算密集型)
多核:四个任务(IO密集型计算密集型)

计算密集型 每个任务都需要10s
多核
多进程:总耗时 10+
多线程:总耗时 40+
IO密集型
多核
多进程:相对浪费资源
多线程:更加节省资源

所以多进程和多线程都有各自的优势并且我们后面在写项目的时候通常可以多进程下面再开设多线程这样的话既可以利用多核也可以介绍资源消耗。

原文地址:https://www.cnblogs.com/Jicc-J/p/12781559.html