zoukankan      html  css  js  c++  java
  • python------线程

    一、为什么有了进程还要有线程

      进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其它资源,可以提高计算机的利用率。但进程还存在很多缺陷的,主要体现在:

      1:进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

      2:进程在执行的过程中如果阻塞,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

    线程的出现就是为了解决进程的缺陷。

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

    每一个进程中至少有一个线程。

    二、线程和进程的区别

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

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

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

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

    三、线程的特点

      1:轻型实体

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

      3:共享进程资源

      4:可并发执行

    四、用户级线程和内核级线程池

    用户级线程和内核级线程池的区别:

      1:内核支持线程是OS内核可感知的,而用户级线程是OS内核不可感知的。

      2:用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言(如Java)这一级处理的;而内核支持线程的创建、撤消和调度都需OS内核提供支持,而且与进程的创建、撤消和调度大体是相同的。

      3:用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。

      4:在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。

      5:用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。

    用户级线程的优缺点:

    优点:

      1:线程的调度不需要内核直接参与,控制简单。

      2:可以在不支持线程的操作系统中实现。

      3:创建和销毁线程、线程切换代价等线程管理的代价比内核线程少得多。

      4:允许每个进程定制自己的调度算法,线程管理比较灵活。

      5:线程能够利用的表空间和堆栈空间比内核级线程多。

      6:同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起。另外,页面失效也会产生同样的问题。

    缺点:资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用

    内核线程的优缺点:

    优点:当有多个处理机时,一个进程的多个线程可以同时执行。

    缺点:由内核进行调度。

    五、threading模块

    线程的创建

    from threading import Thread
    import time
    def sayhi(name):
        time.sleep(2)
        print('%s say hello' %name)
    
    if __name__ == '__main__':
        t=Thread(target=sayhi,args=('hjm',))
        t.start()
        print('主线程')
    创建线程的方式1
    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('hjm')
        t.start()
        print('主线程')
    创建线程的方式2

    多线程与多进程

    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())
    多线程与多进程pid的比较
    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
        '''
    开启效率的比较

     Thread实例对象方法:

      isAlive(): 返回线程是否活动的

      getName(): 返回线程名

      setName(): 设置线程名

    threading模块提供的一些方法:

      threading.currentThread(): 返回当前的线程变量

      threading.enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

      threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate()) 有相同的结果

    from threading import Thread
    import time
    def sayhi(name):
        time.sleep(2)
        print('%s say hello' %name)
    
    if __name__ == '__main__':
        t=Thread(target=sayhi,args=('hjm',))
        t.start()
        t.join()
        print('主线程')
        print(t.is_alive())
        '''
        hjm say hello
        主线程
        False
        '''
    join方法

    六、守护线程

    进程和线程都应该遵循:守护XX会等待主XX运行完毕后被销毁。

    运行完毕并非终止运行。

     1:对主进程来说,运行完毕指的是主进程代码运行完毕。

     2:对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程全部运行完毕,主线程才算运行完毕。

    from threading import Thread
    import time
    def sayhi(name):
        time.sleep(2)
        print('%s say hello' %name)
    
    if __name__ == '__main__':
        t=Thread(target=sayhi,args=('hjm',))
        t.setDaemon(True) #必须在t.start()之前设置
        t.start()
    
        print('主线程')
        print(t.is_alive())
        '''
        主线程
        True
        '''
    守护线程例1
    from threading import Thread
    import time
    def foo():
        print(123)
        time.sleep(1)
        print("end123")
    
    def bar():
        print(456)
        time.sleep(3)
        print("end456")
    
    
    t1=Thread(target=foo)
    t2=Thread(target=bar)
    
    t1.daemon=True
    t1.start()
    t2.start()
    print("main-------")
    
    '''
    123
    456
    main-------
    end123
    end456
    
    '''
    守护线程例2

    七、锁

    同步锁

    from threading import Thread
    import os,time
    def work():
        global n
        temp=n
        time.sleep(0.1)
        n=temp-1
    if __name__ == '__main__':
        n=100
        l=[]
        for i in range(100):
            p=Thread(target=work)
            l.append(p)
            p.start()
        for p in l:
            p.join()
    
        print(n) #结果可能为99
    多个线程抢占资源的情况
    from threading import Thread,Lock
    import os,time
    def work():
        global n
        lock.acquire()
        temp=n
        time.sleep(0.1)
        n=temp-1
        lock.release()
    if __name__ == '__main__':
        lock=Lock()
        n=100
        l=[]
        for i in range(100):
            p=Thread(target=work)
            l.append(p)
            p.start()
        for p in l:
            p.join()
    
        print(n) #结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全
    同步锁的引用
    #不加锁:并发执行,速度快,数据不安全
    from threading import current_thread,Thread,Lock
    import os,time
    def task():
        global n
        print('%s is running' %current_thread().getName())
        temp=n
        time.sleep(0.5)
        n=temp-1
    
    
    if __name__ == '__main__':
        n=100
        lock=Lock()
        threads=[]
        start_time=time.time()
        for i in range(100):
            t=Thread(target=task)
            threads.append(t)
            t.start()
        for t in threads:
            t.join()
    
        stop_time=time.time()
        print('主:%s n:%s' %(stop_time-start_time,n))
    
    '''
    Thread-1 is running
    Thread-2 is running
    ......
    Thread-100 is running
    主:0.5154428482055664 n:99
    '''
    互斥锁与join的区别

    死锁和递归锁

    进程和线程都有死锁和递归锁

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

    from threading import Lock
    from threading import Thread
    import time
    kz = Lock()  #筷子锁
    m = Lock()  #面锁
    
    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)
        kz.release()
        m.release()
    Thread(target=eat,args=('张三',)).start()
    Thread(target=eat2,args=('李四',)).start()
    Thread(target=eat,args=('王五',)).start()
    Thread(target=eat2,args=('马六',)).start()
    
    '''
    张三拿到筷子了
    张三拿到面了
    张三吃面
    李四拿到面了
    王五拿到筷子了
    '''
    死锁

    递归锁可以解决死锁带来的问题。

    from threading import RLock
    from threading import Thread
    import time
    kz = m = 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)
        kz.release()
        m.release()
    Thread(target=eat,args=('张三',)).start()
    Thread(target=eat2,args=('李四',)).start()
    Thread(target=eat,args=('王五',)).start()
    Thread(target=eat2,args=('马六',)).start()
    递归锁

    八、信号量

    信号量和线程池有什么区别?

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

     不同点:开的线程数不一样,对于线程池来说,假设一直就只开5个线程,信号量有几个任务就开几个线程

    from threading import Semaphore
    from threading  import Thread
    import time
    import random
    def func(n,sem):
        sem.acquire()
        print('thread -%s start'%n)
        time.sleep(random.randint(1,3))
        print('thread -%s done' % n)
        sem.release()
    sem = Semaphore(5)  #一把锁有5把钥匙
    for i in range(20):
        Thread(target=func,args=(i,sem)).start()
    信号量

    九、事件

    事件的方法:

     event.isSet():返回event的状态值;

     event.wait():如果 event.isSet()==False将阻塞线程;

     event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

     event.clear():恢复event的状态值为False。

    import time
    import random
    from threading import Event
    from threading import Thread
    def conn_mysql():  # 连接数据库
        count = 1
        while not e.is_set():  # 当事件的flag为False时才执行循环内的语句
            if count>3:
                raise TimeoutError
            print('尝试连接第%s次'%count)
            count += 1
            e.wait(0.5)  # 一直阻塞变成了只阻塞0.5
        print('连接成功')  # 收到check_conn函数内的set指令,让flag变为True跳出while循环,执行本句代码
    
    def check_conn():
        '''
        检测数据库服务器的连接是否正常
        '''
        time.sleep(random.randint(1,2))  # 模拟连接检测的时间
        e.set() # 告诉事件的标志数据库可以连接
    
    e = Event()
    check = Thread(target=check_conn)
    check.start()
    conn = Thread(target=conn_mysql)
    conn.start()
    Event

    十、条件

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

    import threading
    def run(n):
        con.acquire()
        con.wait()  # 等着
        print("run the thread: %s" % n)
        con.release()
    
    if __name__ == '__main__':
        con = threading.Condition()   # 条件  = 锁 + wait的功能
        for i in range(10):
            t = threading.Thread(target=run, args=(i,))
            t.start()
    
        while True:
            inp = input('>>>')
            if inp == 'q':
                break
            con.acquire()        # condition中的锁 是 递归锁
            if inp == 'all':
                con.notify_all()
            else:
                con.notify(int(inp))   # 传递信号 notify(1) --> 可以放行一个线程
            con.release()
    条件

    十一、定时器

    定时器:指定n秒后执行某个操作

    import threading
    def run(n):
        con.acquire()
        con.wait()  # 等着
        print("run the thread: %s" % n)
        con.release()
    
    if __name__ == '__main__':
        con = threading.Condition()   # 条件  = 锁 + wait的功能
        for i in range(10):
            t = threading.Thread(target=run, args=(i,))
            t.start()
    
        while True:
            inp = input('>>>')
            if inp == 'q':
                break
            con.acquire()
            con.notify(int(inp))   # 传递信号 notify(1) --> 可以放行一个线程
            con.release()
            print('****')
    定时器

    十二、线程队列

    LifoQueue后进先出
    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
    '''
    queue.Queue()先进先出
    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())
    
    '''
    (1, 'b')
    (1, 'z')
    (2, 'd')
    '''
    queue.PriorityQueue() 存储数据时可设置优先级的队列

    十三、concurrent.futures

    #1 介绍
    concurrent.futures模块提供了高度封装的异步调用接口
    ThreadPoolExecutor:线程池,提供异步调用
    ProcessPoolExecutor: 进程池,提供异步调用
    Both implement the same interface, which is defined by the abstract Executor class.
    
    #2 基本方法
    #submit(fn, *args, **kwargs)
    异步提交任务
    
    #map(func, *iterables, timeout=None, chunksize=1) 
    取代for循环submit的操作
    
    #shutdown(wait=True) 
    相当于进程池的pool.close()+pool.join()操作
    wait=True,等待池内所有任务执行完毕回收完资源后才继续
    wait=False,立即返回,并不会等待池内的任务执行完毕
    但不管wait参数为何值,整个程序都会等到所有任务执行完毕
    submit和map必须在shutdown之前
    
    #result(timeout=None)
    取得结果
    
    #add_done_callback(fn)
    回调函数
    介绍与基本方法
    开启线程需要成本,成本比开启进程要低
    高IO的情况下,开多线程
    所以我们也不能开启任意多个线程
    futures.ThreadPoolExecutor  # 线程池
    futures.ProcessPoolExecutor  # 进程池
    import time
    import random
    from concurrent import futures
    def funcname(n):
        print(n)
        time.sleep(random.randint(1,3))
        return n * '*'
    thread_pool = futures.ThreadPoolExecutor(5)
    futures.ThreadPoolExecutor()
    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    
    import os,time,random
    def task(n):
        print('%s is runing' %os.getpid())
        time.sleep(random.randint(1,3))
        return n**2
    
    if __name__ == '__main__':
    
        executor=ProcessPoolExecutor(max_workers=3)
    
        futures=[]
        for i in range(11):
            future=executor.submit(task,i)
            futures.append(future)
        executor.shutdown(True)
        print('+++>')
        for future in futures:
            print(future.result())
    ProcessPoolExecutor
    import time
    import random
    from concurrent import futures
    def funcname(n):
        print(n)
        time.sleep(random.randint(1,3))
        return n * '*'
    thread_pool = futures.ThreadPoolExecutor(5)
    thread_pool.map(funcname,range(10))  # map,天生异步,接收可迭代对象的数据,不支持返回值
    map的用法
    
    
    

       

  • 相关阅读:
    Java18(泛型,反射,内省)
    Java17(线程池、Runnable和Callable、Lock、信号量、任务调度、Timer)
    Javaday16(守护线程,线程同步,线程通讯)
    面试题错题集04(异常)
    面试题错题集03(数组)
    面试题错题集02(面向对象)
    Javaday15(Queue,Collections工具类,多线程)
    Java中的内部接口
    ISO14229:2013 之 通过标志读数据ReadDataByIdentifier (0x22)
    Oracle数据库迁移至PostgreSQL数据库问题及解决
  • 原文地址:https://www.cnblogs.com/huangjm263/p/8422883.html
Copyright © 2011-2022 走看看