第二十九章:初识线程

什么是线程

线程是操作系统最小的运算调度单位,被包含在进程中,一个线程就是一个固定的 执行流程 

线程的进程的关系  重点

线程不能单独存在 必须存在于进程中,

进程是一个资源单位,其包含了运行程序所需的所有资源

线程才是真正的执行单位

没有线程,进程中的资源无法被利用起来,所以一个进程至少包含一个线程,称之为主线程

当我们启动一个程序时,操作系统就会自己为这个程序创建一个主线程

线程可以由程序后期开启  ,自己开启线程称之为子线程 

 

为什么需要线程   重点

目的只有一个就是提高效率

就像一个车间 如果产量更不上 就在造一条流水线

当然可以再造一个新车间,那需要把原材料运过去  ,这个过程是非常耗时的 

所以通常情况是创建新的流水线 而不是车间  即 线程

 

如何使用   重点

使用方法和多进程一模一样  

不过开启线程的代码可以放在任何位置  开启进程必须放在判断下面

from threading import Thread,current_thread
import time

def task():
   print("2",current_thread())
   print("子线程running")
   time.sleep(10)
   print("子线程over")

# 使用方法一 直接实例化Thread类
if __name__ == '__main__':
   t = Thread(target=task)
   t.start()
   
   # task()
   # 执行顺序不固定 如果开启线程速度足够快 可能子线程先执行
   print("主线程over")
   print("1",current_thread())

# 使用方法二 继承Thread 覆盖run方法
class MyThread(Thread):
   def run(self):
       print("子线程run!")
m = MyThread()
print("主线over")

# 使用方法和多进程一模一样   开启线程的代码可以放在任何位置 开启进程必须放在判断下面

线程的特点  重点

1.创建开销小

2.同一个进程中的多个线程数据时共享的 

3.多个线程之间,是平等的没有父子关系    所有线程的PID都是相同的 

# 创建线程开销对比
import os
from threading import  Thread
from multiprocessing import Process

import time


def task():
   # print("hello")
   print(os.getpid())
   pass

if __name__ == '__main__':

   st_time = time.time()

   ts = []
   for i in range(100):
       t = Thread(target=task)
       # t = Process(target=task)
       t.start()
       ts.append(t)

   for t in ts:
       t.join()

   print(time.time()-st_time)
   print("主over")

 

守护线程  了解

一个线程可以设置为另一个线程的守护线程 

特点:  被守护线程结束后守护线程也随之结束 

# 守护线程会等到所有非守护线程结束后结束  !    前提是除了主线程之外 还有后别的非守护
# 当然如果守护线程已经完成任务 立马就结束了
from threading import Thread
import time

def task():
   print("子1running......")
   time.sleep(100)
   print("子1over......")

def task2():
   print("子2running......")
   time.sleep(4)
   print("子2over......")
   
t = Thread(target=task)
t.daemon = True
t.start()

t2 =Thread(target=task2)
t2.start()

print("主over")

线程 互斥锁

共享意味着竞争

线程中也存在安全问题,

多线程可以并发执行,一旦并发了并且访问了同一个资源就会有问题 

解决方案:还是互斥锁 

案例:

from threading import Thread,enumerate,Lock
import time

number = 10

lock = Lock()

def task():
   global number
   lock.acquire()
   a = number
   time.sleep(0.1)
   number = a - 1
   lock.release()

for i in range(10):
   t = Thread(target=task)
   t.start()

for t in enumerate()[1:]:
   # print(t)
   t.join()
   
print(number)

 

死锁问题

from threading import Lock, current_thread, Thread

"""
  死锁问题
  当程序出现了不止一把锁,分别被不同的线程持有, 有一个资源 要想使用必须同时具备两把锁
  这时候程序就会进程无限卡死状态 ,这就称之为死锁
  例如:
      要吃饭 必须具备盘子和筷子   但是一个人拿着盘子 等筷子 另一个人拿着筷子等盘子
   
  如何避免死锁问题  
      锁不要有多个,一个足够
      如果真的发生了死锁问题,必须迫使一方先交出锁
       
"""
import time
# 盘子
lock1 = Lock()

# 筷子
lock2 = Lock()

def eat1():
   lock1.acquire()
   print("%s抢到了盘子" % current_thread().name)
   time.sleep(0.5)
   lock2.acquire()
   print("%s抢到了筷子" % current_thread().name)

   print("%s开吃了!" % current_thread().name)
   lock2.release()
   print("%s放下筷子" % current_thread().name)

   lock1.release()
   print("%s放下盘子" % current_thread().name)


def eat2():
   lock2.acquire()
   print("%s抢到了筷子" % current_thread().name)

   lock1.acquire()
   print("%s抢到了盘子" % current_thread().name)


   print("%s开吃了!" % current_thread().name)


   lock1.release()
   print("%s放下盘子" % current_thread().name)
   lock2.release()
   print("%s放下筷子" % current_thread().name)


t1 = Thread(target=eat1)


t2 = Thread(target=eat2)

t1.start()
t2.start()

可重入锁   了解

Rlock  称之为递归锁或者可重入锁

Rlock不是用来解决死锁问题的

与Lock唯一的区别:
Rlock同一线程可以多次执行acquire 但是执行几次acquire就应该对应release几次
   
如果一个线程已经执行过acquire 其他线程将无法执行acquire

 


from threading import RLock, Lock, Thread

# l = Lock()
#
# l.acquire()
# print("1")
# l.acquire()
# print("2")


l = RLock()

# l.acquire()
# print("1")
# l.acquire()
# print("2")

def task():
   l.acquire()
   print("子run......")
   l.release()


# 主线程锁了一次
l.acquire()
l.acquire()

l.release()
l.release()
t1 = Thread(target=task)
t1.start()

信号量  了解


"""
信号量 了解
Lock RLock

可以现在被锁定的代码 同时可以被多少线程并发访问
Lock 锁住一个马桶 同时只能有一个
Semaphore 锁住一个公共厕所   同时可以来一堆人


用途: 仅用于控制并发访问   并不能防止并发修改造成的问题
"""

from threading import Semaphore, Thread
import time

s = Semaphore(5)
def task():
  s.acquire()
  print("子run")
  time.sleep(3)
  print("子over")
  s.release()

for i in range(10):
  t = Thread(target=task)
  t.start()

 

 

回顾

多线程 

线程是CPU的最小执行单位, 线程本质是一堆代码构成的执行流程  

线程被包含在进程中,   

进程是一个资源单位,其中存储着该程序运行所需所有资源, 可以比喻为一个车间  

线程就是车间中国一条流水线,上面放的是制作产品的具体方法(就是的代码)

 

一个进程至少有一个线程,操作系统在运行一个程序时 会在进程中自动开启一条线程

一个进程中可以有多个线程

同一个进程中的线程共享进程内的数据  

创建线程速度非常快 开销小

线程之间没有父子关系    都是平等的 

 

 

使用方式和进程一致

学习多线程  多进程 都是为了提高效率 ,通过并发执行多个任务

实现并发的方式,多线程和多进程 

 

 

 

 

 

 

 

 

 

 

 

上一篇:Python GIL锁 死锁 递归锁 event事件 信号量


下一篇:谈谈高并发系统的限流