THU.线程

THU.线程

线程

进程

  • 进程是操作系统分配资源的最小单位,一个进程至少有一个主线程,一个进程中可以开启多个线程

有了进程为什么要有线程

进程的缺陷
  • 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了
  • 进程在执行的过程中如果阻塞,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也无法执行
  • 占用资源更多
线程就是将一个进程拆分成一个个可以独立运行的基本单位,进而提高运行效率。

# 进程是资源分配的最小单位,线程是 CPU 调度的最小单位

进程和线程的区别

  1. 进程间相互独立,统一进程的各线程间共享资源
  2. 线程可以直接读写进程数据段来进行通信,但需要进程同步和互斥手段的辅助,以保证数据的一致性
  3. 线程所占用的资源相对于进程更少,因此调度和切换更快
  4. 在多线程操作系统中,进程不是一个可执行的实体
  5. 同一进程中的线程,无法利用多核优势

线程的特点

  • 在多线程的操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用 CPU 的基本单位,是占用资源最小的实体

    1. 轻型实体

      • 线程中的实体几乎不拥有系统资源,仅有一点必不可少的、能保证独立运行的资源
    2. 独立调度和分派的基本单位

      • 在多线程中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位
    3. 共享进程资源

      • 线程在统一进程中的各个线程,都可以共享该进程所拥有的资源,且不必调用内核
    4. 可并发执行

      • 在一个进程中的多个线程之间,可以并发执行;同样。不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力

GIL 全局解释器锁

  • Python 代码的执行由 Python 解释器来控制。Python 在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然在 Python解释器中可以“运行”多个线程,但任意时刻只有一个线程在解释器中运行。

  • 对 Python 解释器的访问由全局解释器锁(GIL)来控制,正是这个锁能保证给同一时刻只有一个线程在运行

  • 在 Python 解释器中,只在 cPython 中有,pypy 解释器是没有 GIL 锁的,因此 pypy 解释器更快

  • 启动一个垃圾回收线程,一个是正常执行的线程

  • 是这里一把(GIL 锁),一个先线程要向执行,必须拿到这把锁

  • 同一时刻,同一进程,可以有多个线程,但只有一个线程在执行

  • 如果是计算密集型(主要靠 CPU,可以利用多核优势),要开进程

  • 如果是 IO 密集型,要开线程

多线程与多进程

pid的比较

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('hello',os.getpid())

if __name__ == '__main__':
    # part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
    t1=Thread(target=work)
    t2=Thread(target=work)
    t1.start()
    t2.start()
    print('主线程/主进程pid',os.getpid())

    # part2:开多个进程,每个进程都有不同的pid
    p1=Process(target=work)
    p2=Process(target=work)
    p1.start()
    p2.start()
    print('主线程/主进程pid',os.getpid())

开启效率的较量

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('hello')

if __name__ == '__main__':
    # 在主进程下开启线程
    t=Thread(target=work)
    t.start()
    print('主线程/主进程')
    '''
    打印结果:
    hello
    主线程/主进程
    '''

    # 在主进程下开启子进程
    t=Process(target=work)
    t.start()
    print('主线程/主进程')
    '''
    打印结果:
    主线程/主进程
    hello
    '''

内存数据的共享问题

from  threading import Thread
from multiprocessing import Process
import os
def work():
    global n
    n=0

if __name__ == '__main__':
    # n=100
    # p=Process(target=work)
    # p.start()
    # p.join()
    # print('主',n) # 毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100


    n=1
    t=Thread(target=work)
    t.start()
    t.join()
    print('主',n) # 查看结果为0,因为同一进程内的线程之间共享进程内的数据

互斥锁(同步锁)

  • 用以保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
import time
from threading import Thread


def minus():
    global n
    temp = n
    time.sleep(0.1)
    n = temp - 1


if __name__ == '__main__':
    n = 10
    l = []
    for i in range(10):
        t = Thread(target=minus, args=(lock,))
        t.start()
        l.append(t)

    for j in l:
        j.join()

    print("主线程>>>: %s" % n)  # 由于 time 模块的暂停功能,导致子进程并未完成对 n 的数据操作过程,而主进程仍然在循环执行,以致所有子进程的 n 取到的值均为 10,因此输出结果为 9。
  • 并发在高效运行的同时会影响数据安全,因此需要增加互斥锁来保证数据安全
import time
from threading import Thread, Lock


def minus(lock):
    global n
    lock.acquire()
    temp = n
    time.sleep(0.1)
    n = temp - 1
    lock.release()


if __name__ == '__main__':
    lock = Lock()
    n = 10
    l = []
    for i in range(10):
        t = Thread(target=minus, args=(lock,))
        t.start()
        l.append(t)

    for j in l:
        j.join()

    print("主线程>>>: %s" % n)  # 输出结果为 0

互斥锁与 join 的区别

  • 互斥锁与 join 都可以保证数据安全,但在 start 后立即 join,任务内的所有代码都是串行执行,而使用互斥锁只有加锁部分即修改共享数据的部分是串行的,因此很明显互斥锁的效率更高

信号量(了解)

  • 信号量(Semaphore),信号量和锁相似,锁同一时间只允许一个对象(进程)通过,信号量同一时间允许多个对象(进程)通过
import time
from threading import Thread, Semaphore

def task(i):
    sm.acquire()
    print("第 %s 辆车进入停车场" % i)
    time.sleep(2)
    print("第 %s 辆车离开停车场" % i)
    sm.release()
    

if __name__ == '__main__':
    print("-----这里有两个停车位-----")
    sm = Semaphore(2)
    for i in range(1, 5):
        t = Thread(target=task, args=(i,))
        t.start()

Event 事件

  • Event 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。
    1. 如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。
    2. 一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。
    3. 如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
import time
from threading import Event, Thread


def ParkingLot(event):
    print("已无空闲车位")
    time.sleep(3)
    print("有空闲车位了")
    event.set()


def car(event, i):
    print("第 %s 辆车正在等待车位" % i)
    event.wait()
    print("第 %s 辆车正在停车" % i)


if __name__ == '__main__':
    event = Event()
    t1 = Thread(target=ParkingLot, args=(event,))
    t1.start()
    for i in range(1, 10):
        t2 = Thread(target=car, args=(event, i))
        t2.start()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值