创建线程(守护线程--数据共享--互斥锁)

一:线程理论

1.什么是线程?
每个进程都有自己的地址空间,即进程空间。一个服务器通常需要接收大量并发请求,为每一个请求都创建一个进程系统开销大、请求响应效率低,因此操作系统引进线程。

因此进程其实是一个资源单位 真正被CPU执行的其实是进程里面的线程

image

  • PCB: pcb是印制电路板,又称印刷线路板。分别是:绝缘底板、连接导线和装配焊接电子元件的焊盘。”
  • TCB: 传输控制块
2.抽象比喻线程
  • 1.线程在进程下行进(单纯的车厢无法运行)
  • 2.一个进程可以包含多个线程(一辆火车可以有多个车厢)
  • 3.不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘
  • )同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
  • 4.进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
  • 5.进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
  • 6.进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
  • 7.进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
  • 8.进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

image

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

image

多个线程共享同一个进程的地址空间中的资源,是对一台计算机上多个进程的模拟,有时也称线程为轻量级的进程。
1.而对一台计算机上多个进程,则共享物理内存、磁盘、打印机等其他物理资源。多线程的运行也多进程的运行类似,是cpu在多个线程之间的快速切换。

2.不同的进程之间是充满敌意的,彼此是抢占、竞争cpu的关系,如果迅雷会和QQ抢资源。而同一个进程是由一个程序员的程序创建,所以同一进程内的线程是合作关系,一个线程可以访问另外一个线程的内存地址,大家都是共享的,一个线程干死了另外一个线程的内存,那纯属程序员脑子有问题。

3.类似于进程,每个线程也有自己的堆栈,不同于进程,线程库无法利用时钟中断强制线程让出CPU,可以调用thread_yield运行线程自动放弃cpu,让另外一个线程运行。

4.线程通常是有益的,但是带来了不小程序设计难度,线程的问题是:

  1. 父进程有多个线程,那么开启的子线程是否需要同样多的线程

  2. 在同一个进程中,如果一个线程关闭了文件,而另外一个线程正准备往该文件内写内容呢?

  因此,在多线程的代码中,需要更多的心思来设计程序的逻辑、保护程序的数据。

二:开设线程的两种方式

1.开设进程需要做哪些操作
1.重新申请一块内存空间
2.将所需的资源全部导入
2.开设线程需要做哪些操作
上述两个步骤都不需要 所以开设线程消耗的资源远比开设进程的少!!!

三:创建线程(简易版)

  • 线程模块
from threading import Thread
1.创建线程
from threading import Thread
import time


def test(name):
    print('%s is running' % name)
    time.sleep(3)
    print('%s is over' % name)


t = Thread(target=test, args=('jason',))
t.start()
print('主')
  • 输出结果
jason is running
主
jason is over
2.类创建线程
from threading import Thread
import time



class MyClass(Thread):
    def __init__(self,name):
        super().__init__()
        self.name = name
    # 父类自带run子进程 自己重写run方法
    def run(self):
        print('%s is running' % self.name)
        time.sleep(3)
        print('%s is over' % self.name)

obj = MyClass('jason')
# 调用父类 创建子进程
obj.start()
print('主线程')
  • 输出结果:
jason is running
主线程
jason is over

四:线程对象的其他方法(模块)

  • 1.join方法
  • 2.获取进程号(验证同一个进程内可以开设多个线程,但是同一个进程内的多个线程数据是共享的)
  • 3.active_count 统计当前正在活跃的线程数
  • 4.current_thread 查看主线程名字

五:join方法创建线程

from threading import Thread,active_count,current_thread
import time
import os


def test(name):
    # 查看子线程  进程进程号
    print(os.getpid())
    # 查看子线程 名字
    print(current_thread().name)
    print('%s is running' % name)
    time.sleep(3)
    print('%s is over' % name)

# 创建子线程
t = Thread(target=test, args=('jason',))
# 执行子线程
t.start()
print(os.getpid())
# 先执行子线程 再执行主线程
t.join()
# 查看主线程名字
print(current_thread().name)
# 查看主线程 进程号
print(active_count())
print('主')

image

  • 输出结果
140264
Thread-1
jason is running
140264
jason is over
MainThread
1
主

五:守护线程

1.什么是守护线程?
1.守护线程会在"该进程内所有非守护线程全部都运行完毕后,守护线程才会结束"。
2.并不是主线程运行完毕后守护线程挂掉。这一点是和守护进程的区别之处!
守护线程守护的是:当前进程内所有的非守护线程!你可以算上主线程理解,也可以不算上理解,因为主线程和守护线程是一起死的。
2.代码实现(守护进程)
from threading import Thread
from multiprocessing import Process
import time
def foo():
    print(123)
    time.sleep(3)
    print("end123")
def bar():
    print(456)
    time.sleep(1)
    print("end456")
if __name__ == '__main__':
    t1=Thread(target=foo)
    t2=Thread(target=bar)
    t1.daemon=True
    t1.start()
    t2.start()
    print("main-------")
  • 输出结果:
123
456
main-------
end456

六:线程数据共享

  • 进程间数据默认是隔离的 但是同一个进程内的多个线程数据是共享的
from threading import Thread
money = 100

def test():
    global money
    money = 999

t = Thread(target=test)
t.start()
t.join()
print(money)

七:线程互斥锁

1.互斥锁能达到什么效果?
锁就可以实现将并发变成串行的效果
2.为什么要用互斥锁?
1.通过执行结果可以地址互斥锁能够保证多个线程访问共享数据不会出现数据错误问题
2.操作同一份数据不能并发,并发情况下操作同一份数据 极其容易造成数据错乱
3.互斥锁对共享数据进行锁定,保证同一时刻只能有一个线程去操作。 

image

3.使用模块
Lock变量,这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁
4.线程互斥锁(搭建)
from threading import Thread, Lock
from multiprocessing import Lock
import time

num = 101

# 修改数值
def test(mutex):
    # 局部修改全局
    global num
    # 上锁(一次只有一个人可以通过)
    mutex.acquire()
    # 先获取num的数值
    tmp = num
    # 模拟延迟效果
    time.sleep(0.1)
    # 定义的变量数据 - 循环访问数量
    tmp -= 1
    num = tmp
    # 释放锁
    mutex.release()

# 定义空字典
t_list = []
# 创建 互斥锁
mutex = Lock()
# 循环1-100
for i in range(100):
    # 创建子线程
    t = Thread(target=test, args=(mutex,))
    # 执行子线程
    t.start()
    # 将执行子线程 添加到空列表
    t_list.append(t)
# 确保所有的子线程全部结束
for t in t_list:
    # 串行 先执行子线程 再执行主线程
    t.join()
print(num)

八:TCP服务端实现并发

posted @ 2022-01-15 00:27  AlexEvans  阅读(95)  评论(0编辑  收藏  举报