多任务-多线程

注意:
因为python存在全局解释器锁(GIL)所以纯Cpython不存在真正的多任务。想要实现多任务可以使用c模块实现真正的多任务。(笔记 多任务-* 不考虑GIL的存在,认为python存在多任务)。

每个进程默认开启一条主线程可以自己开辟多条子线程
使用 threading 可是创建线程 threading 是对较底层模块thread的进一步封装

简单多线程

  1. def 创建一个函数用于开辟线程,开辟的线程执行此函数
  2. 使用threading.Thread() 创建线程
  3. 使用start()启动该线程
  4. 基本代码实现
    import threading
    import time
    
    def saySorry():
        print("亲爱的,我错了,我能吃饭了吗?")
        time.sleep(1)
    
    if __name__ == "__main__":
        for i in range(5):
            # target=saySorry使用函数的引用,不加()加上括号表示直接执行
            t = threading.Thread(target=saySorry)
            t.start() #启动线程,即让线程开始执行 `
  5. 注意点:
    1. 当调用start()时,才会真正的创建线程,并且开始执行
    2. 主线程是默认等待子线程执行结束主线程才结束执行的
  6. 对线程的封装
    1. 为了让每个线程封装的更完美使用threading是可以创建类继承自threading.Thread然后重写run方法即可
    2. 代码实现
      import threading
      import time
      
      class MyThread(threading.Thread):
          def run(self):
              for i in range(3):
                  time.sleep(1)
                  msg = "I'm "+self.name+' @ '+str(i) #name属性中保存的是当前线程的名字
                  print(msg)
      
      
      if __name__ == '__main__':
          t = MyThread()
          t.start()
    3. python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。
  7. 线程的调度是无序的,当线程中有耗时操作类似(sleep)时会阻塞线程选择器会自动选择一个就绪线程进行执行,进而执行别的线程当sleep结束后会进入就绪状态等待被调用。
  8. 小结
    1. 每个线程默认有一个名字,但是python会自动为线程指定一个名字。
    2. 当线程的run()方法结束时该线程完成。
    3. 无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。

互斥锁

  1. 引入:同一进程中线共享全局变量,这样方便多线程之间共享数据。所以存在某一变量同时被多个线程调用,此时就会发生资源竞争问题导致变量数据的错乱。例如两个进程同时对一个变量进行累计 “1”操作是就会发生数据错乱,当每个进程循环多次此操作是效果明显。于是引入互斥锁概念
  2. 互斥锁状态分为:锁定/非锁定
  3. 当一个进程调用某一变量时先将其锁定,此时其他进程不能调用。当且仅当该进程释放资源是其他进程才能调该资源。互斥锁通过保证每次只有一个进程对资源的操作,从而保证多线程情况下数据的正确性
  4. 使用互斥锁是要避免死锁的存在(进程A需要调用变量a、b,进程B也需要调用变量a,b。当A先锁定a事等待b的释放,此时B锁定b等待a的释放此时会发生彼此等待会发生死锁现象。A、B都等待对方释放资源)
  5. 使用互斥锁:
    1. 使用threading模块中的Lock类实例化锁对象
    2. 使用acquire()加锁
    3. 执行操作
    4. 使用release()释放
    5. 注意:如果这个锁之前是没有上锁的,那么acquire不会堵塞。如果在调用acquire对这个锁上锁之前 它已经被 其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止
  6. 互斥锁锁的是谁:个人认为是对变量进行加锁,当对线程加锁时锁定加锁处到释放锁处用到的全局变量。其他线程无权调用只有当释放锁是被加锁全局变量才能被其他线程调用(仅代表个人理解
  7. 互斥锁的代码实现
    import threading
    import time
    
    
    g_num = 0
    
    def test1(num):
        global g_num
        for i in range(num):
            mutex.acquire()  # 上锁
            g_num += 1
            mutex.release()  # 解锁
    
        print("---test1---g_num=%d"%g_num)
    
    def test2(num):
        global g_num
        for i in range(num):
            mutex.acquire()  # 上锁
            g_num += 1
            mutex.release()  # 解锁
    
        print("---test2---g_num=%d"%g_num)
    
    # 创建一个互斥锁
    # 默认是未上锁的状态
    mutex = threading.Lock()
    
    # 创建2个线程,让他们各自对g_num加1000000次
    p1 = threading.Thread(target=test1, args=(1000000,))
    p1.start()
    
    p2 = threading.Thread(target=test2, args=(1000000,))
    p2.start()
    
    # 等待计算完成
    while len(threading.enumerate()) != 1:
        time.sleep(1)
    
    print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)

错误之处欢迎指出

猜你喜欢

转载自blog.csdn.net/jlb1024/article/details/82817159