• 线程


    线程与进程的区别:

     1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。

     2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信 --- 需要进程同步和互斥手段的辅助,以保证数据的一致性。

     3)调试和切换:线程上下文切换比进程上下文切换要快得多。

     4)在多线程操作系统中,进程不是一个可执行的实体。

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

    线程的特点

    在多线程的操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。具体属性如下:

     1)轻型实体

      线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。

      线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)

    TCB包括以下信息:
    (1)线程状态。
    (2)当线程不运行时,被保存的现场资源。
    (3)一组执行堆栈。
    (4)存放每个线程的局部变量主存区。
    (5)访问同一个进程中的主存和其它资源。
    用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。
    TCB包括以下信息

     2)独立调度和分派的基本单位。

     在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,帮线程的切换非常迅速且开销小(在同一进程中的)。

     3)共享进程资源。

     线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程id,这意味着,线程可以访问该进程的每一个内存资源,此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。

     4)可并发执行。

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

    内存中的线程

     多个线程共享同一个进程的地址空间中的资源,是对一台计算机上多个进程的模拟,有时也称线程为轻量级的进程。

     而对一台计算机上多个进程,则共享物理内存、磁盘、打印机等其他物理资源。多线程的运行也多进程的运行类似,是cpu在多个线程之间的快速切换。

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

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

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

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

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

    线程又分为两种:

     一、用户级线程:

      内核的切换由用户态程序自己控制内核切换,不需要内核干涉,少了进出内核态的消耗,但不能很好的利用多核CPU。

     二、内核级线程:

      切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态,可以很好的利用smp,即利用多核cpu。windows线程就是这样。

    线程和python

    全局解释器锁GIL

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

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

     在多线程环境中,Python虚拟机按以下方式执行:

     a.设置GIL

     b.切换到一个线程去运行

     c.运行指定数量的字节码指令或者线程主动让出控制(可以调用time.sleep(0))

     d.把线程设置为睡眠状态

     e.解锁GIL

     再次重复以上所有步骤。

    在调用外部代码(如C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

    python线程模块的选择

    Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。
      避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与threading出现冲突;其次低级别的thread模块的同步原语很少(实际上只有一个),而threading模块则有很多;再者,thread模块中当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作,至少threading模块能确保重要的子线程退出后进程才退出。 

      thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。而threading模块支持守护线程,守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。

    threading模块

    multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性。

     线程: 

    from threading import Thread
    def func():
        print('hello world')
    
    if __name__ == '__main__':
        t = Thread(target=func)
        t.start()
        print('主线程')

    启动完进程一般先显示主进程的print内容,然后再显示子进程的print内容。

    启动线程时一般先显示子进程的print内容,然后再显示主进程的print内容。与进程正好相反。

    原因:启动线程比启动进程要快。

    import time
    from threading import Thread,currentThread,enumerate
    
    def func(i):
        time.sleep(1)
        print(i,currentThread().name,currentThread().ident)  #返回名字和进程id
    for i in range(10):
        t = Thread(target=func,args=(i,))
        t.start()
    print(enumerate())  #返回正在运行着的线程列表

     线程实现socket

    import socket
    from threading import Thread
    
    def func(conn):
        while True:
            ret = conn.recv(1024)
            print(ret)
            msg = input('>>>')
            conn.send(msg.encode('utf-8'))
    
    if __name__ == '__main__':
        sk = socket.socket()
        sk.bind(('127.0.0.1',8080))
        sk.listen()
        while True:
            conn,addr = sk.accept()
            p  = Thread(target=func,args=(conn,))
            p.start()
        conn.close()
        sk.close()
    server
    import socket
    s = socket.socket()
    s.connect(('127.0.0.1',8080))
    while True:
        msg = input('>>>')
        s.send(msg.encode('utf-8'))
        data  = s.recv(1024)
        print(data)
    client

     守护进程

    import time
    from threading import Thread
    def func():
        print('开始执行子线程')
        time.sleep(2)
        print('子线执行程完毕')
    
    
    t = Thread(target=func)
    t.setDaemon(True)
    t.start()
    守护线程

    from threading import Thread
    from threading import Lock
    def func():
        global n
        n -= 1
    
    n = 100
    t_list = []
    lock = Lock
    for i in range(100):
        t = Thread(target=func)
        t.start()
        t_list.append(t)
    [t.join() for t in t_list]
    print(n)
    正常情况下不需要锁
    import time
    from threading import Thread
    from threading import Lock
    def func():
        global n
        time.sleep(2)
        lock.acquire()
        temp = n
        time.sleep(0.01)
        n = temp - 1
        lock.release()
    
    n = 100
    t_list = []
    lock = Lock()
    for i in range(100):
        t = Thread(target=func)
        t.start()
        t_list.append(t)
    [t.join() for t in t_list]
    print(n)
    特殊情况下需要加锁

    死锁与递归锁

    死锁:指两个或两个以上的进程或线程的执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

    解决锁问题:用RLock 递归锁解决死锁问题。

    import time
    from threading import RLock
    from threading import Thread
    m = kz = RLock()
    def eat(name):
        kz.acquire()
        print('%s拿到筷子了'%name)
        m.acquire()
        print('%s拿到面了'%name)
        print('%s吃面'%name)
        m.release()
        kz.release()
    
    def eat2(name):
        m.acquire()
        print('%s拿到面了'%name)
        time.sleep(1)
        kz.acquire()
        print('%s拿到筷子了'%name)
        print('%s吃面'%name)
        m.release()
        kz.release()
    
    if __name__ == '__main__':
        Thread(target=eat,args=('aaa',)).start()
        Thread(target=eat2, args=('bbb',)).start()
        Thread(target=eat, args=('ccc',)).start()
        Thread(target=eat2, args=('ddd',)).start()
    递归锁RLock

    信号量

    信号量和线程池区别:

    相同点:在信号量acquire之后,和线程池一样  同时在执行的只能有n个

    不同点:开的线程数不一样  线程池来说 一共就只开5个线程 信号量有几个任务就开几个线程

    import time
    import random
    from threading import Thread
    from threading import Semaphore
    def func(n,sem):
        sem.acquire()
        print('thread -%s start'%n)
        time.sleep(random.random())
        print('thread -%s done'%n)
        sem.release()
    
    sem = Semaphore()
    for i in range(10):
        Thread(target=func,args=(i,sem)).start()

     事件Event

    线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

    event.isSet():返回event的状态值;
    event.wait():如果 event.isSet()==False将阻塞线程;
    event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
    event.clear():恢复event的状态值为False。

    练习:模仿连接数据库,每0,5秒连接一次,创建一个事件标志数据库连接情况,连接成功打印成功,连接超过三次报错TimeoutError

    import time
    import random
    from threading import Event
    from threading import Thread
    def conn_mysql():
        count = 1
        while not e.is_set():
            if count > 3:
                raise TimeoutError
            print('尝试第%s次连接'%count)
            count += 1
            e.wait(0.5)
        print('连接成功')
    
    def check_conn():
        time.sleep(random.randint(1,2))
        e.set()
    
    if __name__ == '__main__':
        e = Event()
        check = Thread(target=check_conn)
        check.start()
        conn = Thread(target=conn_mysql)
        conn.start()

    条件

    使线程等待,只有满足条件时,才释放n个线程

    Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。
    import threading
    def run(n):
        con.acquire()
        con.wait()
        print('run the thread: %s'%n)
        con.release()
    if __name__ == '__main__':
        con = threading.Condition()
        for i in range(10):
            t = threading.Thread(target=run,args=(i,))
            t.start()
        while True:
            msg = input('>>>')
            if msg == 'q':
                break
            con.acquire()
            if msg == 'all':
                con.notify_all()
            else:
                con.notify(int(msg))
            con.release()
    例子

    定时器

    from threading import Timer
    def func():
        print('hello')
    
    t = Timer(3,func)  #定时开启一个线程,执行一个任务  时间是s  执行名是函数名
    t.start()

    线程队列

    import queue
    l = queue.Queue()
    l.put(1)
    l.put(2)
    l.put(3)
    print(l.get())
    print(l.get())
    print(l.get())
    先进先出
    import queue
    l = queue.LifoQueue()
    l.put(1)
    l.put(2)
    l.put(3)
    print(l.get())
    print(l.get())
    print(l.get())
    后进先出
    import queue
    pq = queue.PriorityQueue()  # 值越小越优先,值相同就asc码小的先出
    pq.put((1,'z'))
    pq.put((1,'b'))
    pq.put((15,'c'))
    pq.put((2,'d'))
    print(pq.get())
    print(pq.get())
    print(pq.get())
    特殊,值越小越优先,值 一样ascll小优先

    concurrent.futures

    import time
    import random
    from concurrent import futures
    def func(n):
        print(n)
        time.sleep(random.randint(1,3))
        return n*'*'
    
    thread_poll = futures.ThreadPoolExecutor(5)
    f_lst = []
    for i in range(10):
        f = thread_poll.submit(func,i)
        f_lst.append(f)
    [print(f.result()) for f in f_lst]
    ThreadPoolExecutor用法
    import time
    import random
    def func(n):
        print(n)
        time.sleep(random.randint(1,3))
        return n*'*'
    
    thread_poll = futures.ThreadPoolExecutor(5)
    thread_poll.map(func,range(10))   #可以调用函数,但不能接收返回值
    map用法
    import time
    import random
    from concurrent import futures
    def func(n):
        print(n)
        time.sleep(random.randint(1,3))
        return n*'*'
    
    def call(args):
        print(args.result())
    
    thread_poll = futures.ThreadPoolExecutor(5)
    f_lst = []
    for i in range(10):
        f = thread_poll.submit(func,i).add_done_callback(call)
        f_lst.append(f)
    回调函数

     生产者消费模型:

    import time
    import random
    from threading import Thread
    import queue
    def producer(q,food):
        for i in range(10):
            q.put('%s-%s'%(food,i))
            print('生产了%s-%s'%(food,i))
            time.sleep(random.random())
        q.join()
    
    def consumer(q,name):
        while True:
            food = q.get()
            print('%s 吃了 %s'%(name,food))
            q.task_done()
    
    if __name__ == '__main__':
        q = queue.Queue()
        p1 = Thread(target=producer,args=(q,'米饭'))
        p1.start()
        p2 = Thread(target=producer,args=(q,'面条'))
        p2.start()
        c1 = Thread(target=consumer,args=(q,'aaa'))
        c1.daemon = True
        c1.start()
        c2 = Thread(target=consumer,args=(q,'bbb'))
        c2.daemon = True
        c2.start()
        c3 = Thread(target=consumer,args=(q,'ccc'))
        c3.daemon = True
        c3.start()
        p1.join()
        p2.join()
    生产者消费者模型
  • 相关阅读:
    20172328《程序设计与数据结构》实验一报告
    20172328《程序设计与数据结构》第三周学习总结
    20172328《程序设计与数据结构》第二周学习总结
    20172328《程序设计与数据结构》第一周学习总结
    预备作业03
    预备作业02
    寒假作业01
    竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生
    Java全栈第二篇-IDEA的安装及教育包领取(高校免费一年)和入门教程
    Java全栈第一篇-流程控制语句
  • 原文地址:https://www.cnblogs.com/tsboy/p/8422810.html
Copyright © 2020-2023  润新知