Python全栈学习笔记day 39:线程(全局解释器锁GIL、threading模块)

目录

一、线程

1.1  进程和线程的关系

1.2 线程的特点

1.3 内存中的线程

1.4   线程启动格式代码:

1.5  练习 :多线程实现socket

二、全局解释器锁GIL

三、Thread类的其他方法



一、线程

进程是资源分配的最小单位,线程是CPU调度的最小单位.每一个进程中至少有一个线程。 

 

1.1  进程和线程的关系

Python全栈学习笔记day 39:线程(全局解释器锁GIL、threading模块)_第1张图片

线程与进程的区别可以归纳为以下4点:
  1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
  2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
  3)调度和切换:线程上下文切换比进程上下文切换要快得多。
  4)在多线程操作系统中,进程不是一个可执行的实体。

通过漫画了解进程

 

1.2 线程的特点

 

1)轻型实体
  线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
  线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。
2)独立调度和分派的基本单位。
  在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
3)共享进程资源。
  线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程id,这意味着,线程可以访问该进程的每一个内存资源;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
4)可并发执行。
  在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。

 

1.3 内存中的线程

Python全栈学习笔记day 39:线程(全局解释器锁GIL、threading模块)_第2张图片

 

1.4   线程启动格式代码:

方法一:和多进程差不多

import os
from threading import Thread
多线程并发
def func(a,b):
    global g
    g = 0
    print(g,os.getpid())

g = 100
t_lst = []
for i in range(10):
    t = Thread(target=func,args=(i,5))
    t.start()
    t_lst.append(t)
for t in  t_lst : t.join()
print(g)


0 5408
0 5408
0 5408
0 5408
0 5408
0 5408
0 5408
0 5408
0 5408
0 5408
0

可以使用global修改主进程中的变量。

方法二:面向对象

import os
import time
from threading import Thread

class MyTread(Thread):
    def __init__(self,args):
        super().__init__()
        self.args = args
    def run(self):
        time.sleep(1)
        print(self.args)

t = MyTread(10)
t.start()

 

总结:

# 进程是最小的内存分配单位
# 线程是操作系统调度的最小单位
# 线程直接被CPU执行,进程内至少含有一个线程,也可以开启多个线程
    # 开启一个线程所需要的时间要远远小于开启一个进程
    # 多个线程内部有自己的数据栈,数据不共享
    # 全局变量在多个线程之间是共享的

 

1.5  练习 :多线程实现socket

client端:

import socket

sk = socket.socket()
sk.connect(('127.0.0.1',8080))

msg = sk.recv(1024)
print(msg)
inp = input('>>> ').encode('utf-8')
sk.send(inp)
sk.close()

serve端:

import socket
from threading import Thread

def chat(conn):
    conn.send(b'hello')
    msg = conn.recv(1024).decode('utf-8')
    print(msg)
    conn.close()

sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
while True:
    conn,addr = sk.accept()
    Thread(target=chat,args = (conn,)).start()
sk.close()

 

 

二、全局解释器锁GIL

图解:(锁能保证同一时刻只有一个线程读取数据)

Python全栈学习笔记day 39:线程(全局解释器锁GIL、threading模块)_第3张图片

在多线程环境中,按以下方式执行:

  a、设置 GIL;

  b、切换到一个线程去运行;

  c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));

  d、把线程设置为睡眠状态;

  e、解锁 GIL;

  d、再次重复以上所有步骤。

 

在Cpython解释器下的python程序 在同一时刻 多个线程中只能有一个线程被CPU执行
高CPU : 计算类 --- 高CPU利用率    多进程(计算复杂)
高IO  : 爬取网页 200个网页        多线程(高并发)
        qq聊天   send recv
        处理日志文件 读文件
        处理web请求
        读数据库 写数据库

 

三、Thread类的其他方法

multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,

Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程变量。
  # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

import time
import threading

def wahaha(n):
    time.sleep(0.5)
    print(n,threading.current_thread(),threading.get_ident())

for i in  range(10):
    threading.Thread(target=wahaha,args=(i,)).start()
print(threading.active_count())    # 一共有几个活跃的线程
print(threading.current_thread())   # 返回线程名称和id
print(threading.enumerate())        # 列表的形式返回所有线程名称和id

 

你可能感兴趣的:(并发编程)