线程

线程

一、初识线程

在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程在工厂中, 每个车间都有房子,而且每个车间默认就有一条流水线.

操作系统 ===> 工厂

进程 ===> 车间

线程 ===> 流水线

cpu ===> 电源

线程:cpu最小的执行单位

进程:资源集合/资源单位

线程运行 = 运行代码

进程运行 = 各种资源 + 线程

进程等待所有线程结束才会结束

归纳起来,可以这样说,操作系统可以同时执行多个任务,每一个任务就是一个进 程;进程可以同时执行多个任务,每一个任务就是一个线程进程其实指的是一个资源单位或者说资源集合--》开了空间之后里面放了代码,而代码需要去运行--》而代码的运行过程则是线程。

在右键运行程序的时候:申请内存空间,先把解释器代码丢进去并且把python代码丢进去(进程做得),运行代码(线程做得)。

进程和线程的区别:进程---》资源的申请与销毁的过程;线程---》单指代码的运行过程

总的来说,线程是进程的组成部分,一个进程可以拥有多个线程。在多线程中,会有一个主线程来完成整个进程从开始到结束的全部操作,而其他的线程会在主线程的运行过程中被创建或退出。

当进程被初始化后,主线程就被创建了,对于绝大多数的应用程序来说,通常仅要求有一个主线程,但也可以在进程内创建多个顺序执行流,这些顺序执行流就是线程。

二、开启线程的两种方式

# 方式一:调用Thread类创建线程
from threading import Thread
import time

def task():
    print('子线程 start')
    time.sleep(2)
    print('子线程 end')

t = Thread(target=task)  # 初始化一个线程对象
t.start()  # 告诉操作系统开一个线程
print('主线程')
# 方式二:继承Thread类创建线程类
from threading import  Thread
import time

class Myt(Thread):
    def run(self):
        print('子线程 start')
        time.sleep(5)
        print('子线程 end')

t = Myt()
t.start()
print('主线程')

三、子线程与子进程的创建速度对比

from threading import Thread
from multiprocessing import Process
import time

def task(name):
    print(f"{name} is running")
    time.sleep(2)
    print(f"{name} is end")

if __name__ == '__main__':
    t = Thread(target=task,args=('子线程',))
    p = Process(target=task,args=('子进程',))
    t.start()
    p.start()
    print('主')

上述代码,开启子线程的打印效果是:

子线程 is running
主
子线程 is end

开启子进程的打印效果是:

主
子进程 is running
子进程 is end

从以上两个结果进行对比:

  • 开启子进程需要申请资源开辟空间,所以创建速度慢
  • 开启子线程只是告诉操作系统一个执行方案,所以创建速度快

四、子线程共享资源

from threading import Thread
import time,os

x = 100
def task():
    global x
    x = 50
    print(os.getpid())  # 11088

if __name__ == '__main__':
    t = Thread(target=task)
    t.start()
    time.sleep(2)
    print(x)  # 50  x变成子线程中的50,说明子线程是共享资源的
    print(os.getpid())  # 11088

五、线程的join方法

主线程等待子线程运行结束才结束

Thread 提供了让一个线程等待另一个线程完成的 join() 方法。当在某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到被 join() 方法加入的 join 线程执行完成。

join() 方法通常由使用线程的程序调用,以将大问题划分成许多小问题,并为每个小问题分配一个线程。当所有的小问题都得到处理后,再调用主线程来进一步操作。

from threading import Thread
import time
def task(name,n):
    print(f"{name} start")
    time.sleep(n)
    print(f"{name} end")

if __name__ == '__main__':
    t1 = Thread(target=task,args=('子线程1',1))
    t2 = Thread(target=task,args=('子线程2',2))
    t3 = Thread(target=task,args=('子线程3',3))

    start = time.time()
    t1.start()
    t2.start()
    t3.start()
    t1.join()   # 感知子线程的结束,在这阻塞1s
    t2.join()
    t3.join()   
    end = time.time()  # 3.0039877891540527

    print(end - start)
    print('主线程结束')

对比进程的join,进程的join是当前线程在等子进程运行结束并不影响其他线程

六、线程的其他方法

Thread实例对象的方法:

  • is_alive():返回线程是否活着
  • getName():返回线程名
  • setName():设置线程名

threading模块提供的一些方法(这些模块需要提前导入):

  • threading.currentThread():返回当前的线程变量
  • threading.enumerate():返回一个包含正在运行的线程的list。正在运行是指线程启动后,结束前,不包括启动前和终止后的线程
  • threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
from threading import Thread,current_thread,enumerate,activeCount
import time

def task(name,n):
    print(f"{name}开始")
    time.sleep(n)
    print(f"{name}结束")

if __name__ == '__main__':
    t1 = Thread(target=task,args=('子线程1',1))
    t2 = Thread(target=task,args=('子线程2',2))
    t1.start()
    t2.start()
    print(t1.is_alive())  # 查看t1线程是否活着,True的话证明还活着
    print(t1.getName())  # 获取t1线程名  Thread-1
    t1.setName('超级鸡车')  # 给t1设置线程名
    print(t1.getName())   # 超级鸡车
    print(current_thread())  # 返回当前线程变量,<_MainThread(MainThread, started 7048)>
    print(enumerate())  # 返回当前运行的线程的一个列表,[<_MainThread(MainThread, started 7048)>, <Thread(超级鸡车, started 1216)>, <Thread(Thread-2, started 11432)>]
    print(activeCount())  # 返回当前正在运行的线程数量,3

    print('主线程结束 ')

七、守护线程

守护线程守护的是进程的运行周期

  • 对主进程来说,运行完毕指的是主进程代码运行完毕
  • 对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

有一种线程,它是在后台运行的,它的任务是为其他线程提供服务,这种线程被称为“后台线程(Daemon Thread)”,又称为“守护线程”或“精灵线程”。Python 解释器的垃圾回收线程就是典型的后台线程。

守护线程有一个特征,如果所有的主线程都死亡了,那么守护线程会自动死亡。

调用 Thread 对象的 daemon 属性可以将指定线程设置成守护线程。下面程序将指定线程设置成守护线程,可以看到当所有的主线程都死亡后,守护线程随之死亡。当在整个虚拟机中只剩下守护线程时,程序就没有继续运行的必要了,所以程序也就退出了。

import threading
def task(max):
    for i in range(max):
        print(threading.current_thread().name+" "+str(i))

t = threading.Thread(target=task,args=(100,),name='守护线程')  # 可以在此直接将线程设置成守护线程
t.daemon = True  # 也可以通过daemon = True设置为守护线程
t.start()
for i in range(10):
    print(threading.current_thread().name+" "+str(i))
print('主线程结束')  # 到了这里主线程结束,守护线程也应该随之结束

上面程序中先将 t 线程设置成守护线程(第 10 行代码),然后启动该线程。本来该线程应该执行到 i 等于 99 时才会结束,但在运行程序时不难发现,该守护线程无法运行到 99,因为当主线程也就是程序中唯一的前台线程运行结束后,程序会主动退出,所以守护线程也就被结束了。

从上面的程序可以看出,主线程默认是前台线程,t线程默认也是前台线程。但并不是所有的线程默认都是前台线程,有些线程默认就是后台线程。前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后天线程。

注意,当前台线程死亡后,Python 解释器会通知守护线程死亡,但是从它接收指令到做出响应需要一定的时间。如果要将某个线程设置为守护线程,则必须在该线程启动之前进行设置。也就是说,将 daemon 属性设为 True,必须在 start() 方法调用之前进行,否则会引发 RuntimeError 异常。

八、多线程实现socket

# 服务端
from threading import Thread
import time,socket

soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
soc.bind(('127.0.0.1',8005))
soc.listen(5)

def task(conn,addr):
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:
                break
            print('来自客户端数据>>>',data)
            time.sleep(1)
            conn.send(data.upper())
        except Exception:
            break

while True:
    print('等待客户端连接>>>')
    conn,addr = soc.accept()
    t = Thread(target=task,args=(conn,addr))
    t.start()
    print('有个客户端连接上了>>>',addr)

# 客户端
import socket

soc = socket.socket()
soc.connect(('127.0.0.1',8005))

while True:
    msg = input('请输入要发送的数据>>>').strip()
    soc.send(msg.encode('utf-8'))
    data = soc.recv(1024)
    print('收到来自服务端数据>>>',data)

soc.close()
原文地址:https://www.cnblogs.com/zhuangyl23/p/11537383.html