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的用法


   

原文地址:https://www.cnblogs.com/huangjm263/p/8422883.html