python并发编程之线程

1、为什么要有线程

因为进程虽然有其优点,但也有缺点:
    1.进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
    2.进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

2、线程的出现

正因为进程的缺点,所以需要解决它。在80年代,出现了能独立运行的基本单位——线程(Threads)

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

3、进程和线程的区别

可以归纳为以下4点:

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

4、线程的特点

1.轻型实体
    线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
    线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述,
      TCB用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。

        TCB包括以下信息:

            1.线程状态。
            2.当线程不运行时,被保存的现场资源。
            3.一组执行堆栈。
            4.存放每个线程的局部变量主存区。
            5.访问同一个进程中的主存和其它资源。
2.独立调度和分派的基本单位
    在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。
      由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。 
3.共享进程资源
    线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
4.可并发执行
    在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;
      同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。

5、如何开启线程

方法一:
    from multiprocessing import Process

    from threading import Thread


    def task():
        print("我是子线程")


    if __name__ == '__main__':
        t = Thread(target=task, args=())
        t.start()
        print("主线程")
方法二:重写run方法
    from threading import Thread
    import time
    class Sayhi(Thread):
        def __init__(self,name):
            super().__init__()
            self.name=name
        def run(self):
            time.sleep(2)
            print('%s say hello' % self.name)

    if __name__ == '__main__':
        t = Sayhi('nick')
        t.start()
        print('主线程') 

6、GIL全局解释器锁

1. Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行
2. Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。

  为了实现上述,就有了GIL全局解释器锁

     1、GIL锁它是在python解释器中的, 只在cpython中有, pypy解释器是没有GIL锁的,
     2、起一个垃圾回收线程, 一个是正常执行的线程
     3、设置了一把锁(GIL锁),  一个线程要想执行,必须拿到这把锁
     4、同一时刻,开启一个进程,一个进程中可以有多个线程, 只能有一个线程在执行
     5、如果是计算密集型:要开进程
     6、如果是io密集型:要开线程  

7、线程间数据是共享的

import os
from threading import Thread
def work():
    global n
    n=0 
if __name__ =='__main__':
    n = 1
    t = Thread(target=work,) 
    t.start()
    t.join()
    print('主',n) # 查看结果为0,因为同一进程内的线程之间共享进程内的数据 

8、守护进程

from threading import Thread
import time
def task():
    time.sleep(1)
    print("我是子线程")

if __name__ == '__main__':
    t = Thread(target=task,)
    t.setDaemon(True)  # 开启守护线程,  主线程结束,子线程跟着结束
    t.start()
    print("主线程")

9、Thread类的方法

Thread实例对象的方法:
    isAlive():返回线程是否活动的。
    getName():返回线程名。
    setName():设置线程名。
    join():先运行完子线程,再运行做主线程

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

10、多线程实现socket:

    服务端:
        import multiprocessing
        import threading

        import socket
        s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        s.bind(('127.0.0.1',8080))
        s.listen(5)

        def action(conn):
            while True:
                data=conn.recv(1024)
                print(data)
                conn.send(data.upper())

        if __name__ == '__main__':
            while True:
                conn,addr=s.accept()
                p=threading.Thread(target=action,args=(conn,))
                p.start()
                p1=threading.Thread(target=action,args=(conn,))
                p1.start()
    客户端:
        import socket

        s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        s.connect(('127.0.0.1',8080))

        while True:
            msg=input('>>: ').strip()
            if not msg:continue

            s.send(msg.encode('utf-8'))
            data=s.recv(1024)
            print(data) 

11、信号量(Semaphore)

    # Semaphore:信号量可以理解为多把锁,同时允许多个线程来更改数据
    from threading import Thread,Semaphore
    import time
    import random
    sm=Semaphore(5)

    def task(name):
        sm.acquire()
        print('%s正在蹲坑'%name)

        time.sleep(random.randint(1,3))
        sm.release()

    if __name__ == '__main__':
        for i in range(100):
            t=Thread(target=task,args=(i,))
            t.start() 

12、Even事件

    from threading import Thread, Event
    import time

    def girl(event):
        print("都女士正在恋爱中...")
        time.sleep(3)
        print("都女生分手了")

        # 发出分手的信号
        event.set()

    def boy(i, event):
        print("渣男:%s正在等待都女生分手" % i)
        # 卡住
        event.wait()
        print("渣男:%s开始追了" % i)

    if __name__ == '__main__':
        event = Event() 
        t = Thread(target=girl, args=(event,))
        t.start()

        for i in range(10):
            b_t = Thread(target=boy, args=(i, event))
            b_t.start()

13、同步锁(互斥锁)

    from threading import Thread, Lock
    import time

    def task(lock):
        # 上锁
        lock.acquire()
        global n
        temp = n
        # 并发安全
        # 10个线程都卡在这了
        time.sleep(1)
        # n -= 1  # n=n-1
        n = temp-1
        # 释放锁
        lock.release()
    if __name__ == '__main__':
        n = 10
        ll = []
        lock = Lock()
        for i in range(10):
            t = Thread(target=task, args=(lock, ))
            t.start()
            ll.append(t)
            for j in ll:
                j.join()
            print("主线程:", n)

14、线程队列
queue队列:使用import queue,用法与进程Queue一样

同一个进程下多个线程数据是共享的
  为什么同一个进程下还会去使用队列呢
    因为队列是管道 + 锁
      所以用队列还是为了保证数据的安全

1.先进先出
      import queue

      q=queue.Queue()
      q.put('first')
      q.put('second')
      q.put('third')

      print(q.get())
      print(q.get())
      print(q.get())
      '''
      结果(先进先出):
      first
      second
      third
      '''

2.后进先出
       import queue

      q=queue.LifoQueue()
      q.put('first')
      q.put('second')
      q.put('third')

      print(q.get())
      print(q.get())
      print(q.get())
      '''
      结果(后进先出):
      third
      second
      first
      ''' 
3.优先级队列
    import queue
    q=queue.PriorityQueue()
    #put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
    q.put((20,'a'))
    q.put((10,'b'))
    q.put((30,'c'))

    print(q.get())
    print(q.get())
    print(q.get())
    '''
    结果(数字越小优先级越高,优先级高的优先出队):
    (10, 'b')
    (20, 'a')
    (30, 'c')
    '''  
4.Queue方法
    Queue.qsize():返回队列中目前项目的正确数量
    Queue.empty():如果队列是空的返回True
    Queue.full():如果队列是满的返回True
    Queue.put(item [, block [,timeout ] ] ) :将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,
                                                默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。
                                                  timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。
    Queue.put_nowait(item):等价于put(item, False)
    Queue.get( [ block [ ,timeout ] ] ):返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,
                                            默认为True. 如果设置为False,将引发Queue.Empty异常(定义在Queue模块中)。
                                              timeout是可选超时时间,用在阻塞模式中。
    Queue.get_nowait():等价于get(False)
    Queue.join(): block直到queue被消费完毕。
    Queue.task_done():指示先前进入队列的任务已完成。 由队列消费者线程使用。 对于每个用于获取任务的get(),后续调用task_done()会告诉队列该任务的处理已经完成。  
                      如果join()当前处于阻塞状态,那么当所有项都被处理完时,它将恢复正常(这意味着,对于每个已经被put()放入队列的项,都会收到一个task_done()调用)。  
                      如果调用次数超过队列中放置的项的数量,则引发ValueError。
posted @ 2021-07-22 18:37  zhutianyu  阅读(65)  评论(0)    收藏  举报