python开发之线程

线程

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位,一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。所有在同一个进程中的线程是共享同一块内存空间的。

一个程序执行的实例就是一个进程,进程就是资源的集合。

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

总结一下进程和线程的区别:

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

def func(n):   # 这是子线程
    time.sleep(1)
    print(n)


for i in range(10):      # 这是主线程
    t = Thread(target=func,args=(i,))
    t.start()

多线程启动程序二

from threading import Thread
import time
class Mythread(Thread):
    def __init__(self,args):
        super().__init__()
        self.args = args

    def run(self):
        time.sleep(1)
        print(self.args)

for i in range(10):
    t = Mythread(i)
    t.start()

同一进程之中的数据各线程是共享的

from threading import  Thread
import time

def func(n):   # 这是子线程
    global g
    g=0
    print(g)
g = 100
t_lst = []
for i in range(10):      # 这是主线程
    t = Thread(target=func,args=(i,))
    t.start()
    t_lst.append(t)
for t in t_lst:t.join()
print(g)
# 结果全是0

 全局解释器锁GIL

Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
  对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

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

  a、设置 GIL;

  b、切换到一个线程去运行;

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

  d、把线程设置为睡眠状态;

  e、解锁 GIL;

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

注:1、GIL导致了同一时刻只能有一个线程访问CPU。

       2、锁的是什么?锁的是线程

       3、 GIL是python语言的问题吗?不是,是CPython解释器的特性。

简单比较多线程和多进程的效率

import time
from threading import Thread
from multiprocessing import Process

def func(n):

   return n + 1

if __name__ == '__main__':
    start = time.time()
    t_lst = []
    for i in  range(100):
        t = Thread(target=func,args=(i,))
        t.start()
        t_lst.append(t)

    for t in t_lst:t.join()
    res1 = time.time()-start
   ###############################################
    start1 = time.time()
    t_lst = []
    for i in range(100):
        t = Process(target=func, args=(i,))
        t.start()
        t_lst.append(t)

    for t in t_lst: t.join()
    res2 = time.time() - start1
    print('线程:',res1) #0.04200243949890137
    print('进程:',res2) #7.6134352684021

多线程实现socket

server端

from threading import Thread

import socket

def chat(conn):
    conn.send(b'hello')
    res = conn.recv(1024).decode('utf8')
    print(res)
    conn.close()

sk = socket.socket()
sk.bind(('127.0.0.1',8000))
sk.listen()
while True:
    conn,addr = sk.accept()
    Thread(target=chat,args=(conn,)).start()
sk.close()

client端

import socket

sk = socket.socket()
sk.connect(('127.0.0.1',8000))

msg = sk.recv(1024)
print(msg)
inp = input('>>').encode('utf8')
sk.send(inp)
sk.close()

守护线程

理论一:

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

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

理论二: 

1、主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束。

2、主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。

from threading import Thread
import time

def func1():
    print('双击666')
    time.sleep(10)

def func2():
    print('666')
    time.sleep(5)

t = Thread(target=func1,)
t.daemon=True # 守护线程,主线程结束,守护线程随之结束
t.start()
t1 = Thread(target=func2,)
t1.start()
print('主线程')

# 守护进程随着主进程代码的执行结束而结束。
# 守护线程会在主线程结束之后等待其它子线程的结束才结束。

from threading import Thread,Lock
import time
def func(lock):
    global n
    lock.acquire()
    temp = n
    time.sleep(1)
    n = temp -1
    lock.release()

n = 10
t_lit = []
lock = Lock()
for i in range(10):
    t = Thread(target=func,args=(lock,))
    t.start()
    t_lit.append(t)

for t in t_lit:t.join()
print(n) # 0

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

from threading import Lock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

from threading import RLock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

信号量

Semaphore就是信号量

Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

from threading import Semaphore,Thread
import time
def func(sem,a,b):
    time.sleep(1)
    sem.acquire()
    print(a+b)
    sem.release()

# 同一时间只能有n个线程执行
sem = Semaphore(4) # 表示最多有4个线程执行
for i in range(10):
    t = Thread(target=func,args=(sem,i,i+5,))
    t.start()

事件

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

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

检测mysql数据库连接的实例

import time
from threading import Event,Thread
import random

def connect_db(e):
    count = 0
    while count<3:
        e.wait(1) #状态为false的时候,我只等待1s就结束
        if e.is_set() == True:
            print('连接数据库')
            break
        else:
            count +=1
            print('第%s次连接失败'%count)
    else:
        raise  TimeoutError('数据库连接超时')
def check_web(e):
    time.sleep(random.randint(0,3))
    e.set()

e = Event()
t1 = Thread(target=connect_db,args=(e,))
t2 = Thread(target=check_web,args=(e,))
t1.start()
t2.start()

 条件

Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。

from threading import Condition,Thread

def func(con,i):
    con.acquire()
    con.wait()
    print('in %s'%i)
    con.release()
con = Condition()
for i in range(10):
    Thread(target=func,args=(con,i)).start()
while True:
    num = int(input('>>:'))
    con.acquire()
    con.notify(num) # 发信号,然后给num把钥匙
    con.release()

# wait()和notify都是在acquire和release中,wait()就是在等待钥匙

定时器

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

from threading import Timer

def fun():
    print('时间同步')

t = Timer(2,fun) # 第一个参数是你定的时间,第二个是你要执行的函数
t.start()

线程队列

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

import queue # 队列里内置了很多锁
q = queue.Queue() # 先进先出
q.put(1)
q.put(2)
print(q.get()) #2
print(q.get()) #1
###############################################
l = queue.LifoQueue() # 栈,先进后出
l.put(1)
l.put(2)
print(l.get()) # 2
print(l.get()) # 1
############################################
p = queue.PriorityQueue() # 优先级队列
p.put((30,'c'))
p.put((10,'a'))
p.put((20,'b'))
print(p.get()) #(10, 'a')
print(p.get()) #(20, 'b')
print(p.get()) #(30, 'c')

线程池

#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)
回调函数
from  concurrent.futures import ThreadPoolExecutor
import time

def func(n):
    time.sleep(2)
    print(n)
    return n*n

tpool = ThreadPoolExecutor(max_workers=5) # 线程数一般不要超过5*cpu
t_list = []
for i in range(20):
    t = tpool.submit(func,i) # 异步提交任务
    t_list.append(t)  # 把返回值存到列表中
tpool.shutdown() # 达到join以及close的效果
print('主线程')
for t in t_list:
    print('>>>',t.result()) # 取出结果用result方法

如果是要起进程池,只需要把线程换成进程即可。

 

posted on 2015-11-01 18:21  Mr.Hui  阅读(385)  评论(0编辑  收藏  举报

导航