线程详解(自动化运维-12)
线程概念
进程
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个进程至少有一个线程,线程是操作系统最直接的执行单元。
Python的全局解释器锁
Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
Python threading模块
线程有2种调用方式,如下:
直接调用
import threading,time
def your_name(name):
    print("your name is %s" % name)
    print(threading.current_thread()) # 打印前当前线程
    time.sleep(1)
if __name__ == "__main__":
    name1 = threading.Thread(target=your_name,args=("bushaoxun",)) # target 执行的函数  args 函数的参数,元组的形式。实例化一个线程
    name2 = threading.Thread(target=your_name,args=("shaoxun",))
    name1.start()
    name2.start()
    print(threading.active_count()) # 打印当前活动的线程
    print(name1.getName()) # 打印线程的名字
    print(name2.getName())
# 执行结果
your name is bushaoxun
<Thread(Thread-1, started 18796)>
your name is shaoxun
<Thread(Thread-2, started 9624)>
3
Thread-1
Thread-2
继承式调用
import threading
import time
class Your_name(threading.Thread):
    def __init__(self,name):
        super(Your_name, self).__init__() # 使用父类的构造函数
        self.name = name
    def run(self): # 这里必须为 run ,重构的父类 run() 函数
        print("your name is %s" % self.name)
        print(threading.current_thread())
        time.sleep(2)
name1 = Your_name("bushaoxun") # 实例化一个线程
name2 = Your_name("shaoxun")
name1.start()
name2.start()
print("主线程执行完毕")
# 执行结果
your name is bushaoxun
<Your_name(bushaoxun, started 15740)>
your name is shaoxun
<Your_name(shaoxun, started 19092)>
主线程执行完毕
join() 方法
线程使用 join 方法,等待这个线程执行完毕才执行主线程。
import threading
import time
class Your_name(threading.Thread):
    def __init__(self,name):
        super(Your_name, self).__init__() # 使用父类的构造函数
        self.name = name
    def run(self): # 这里必须为 run ,重构的父类 run() 函数
        print("your name is %s" % self.name)
        print(threading.current_thread())
        time.sleep(2)
        print("%s 执行完毕" % self.name)
name1 = Your_name("bushaoxun")
name2 = Your_name("shaoxun")
name1.start()
name2.start()
name1.join()
name2.join()  # 等待这个线程执行完毕才执行主线程。否则,因为子线程睡了 2 秒主线会先执行,然后等待子线程结束,程序退出。
print("主线程执行完毕")
# 执行结果
your name is bushaoxun
<Your_name(bushaoxun, started 6416)>
your name is shaoxun
<Your_name(shaoxun, started 10808)>
shaoxun 执行完毕  # 只是主线程等待,其余还是并行的.
bushaoxun 执行完毕
主线程执行完毕
守护进程(Daemon)
守护进程: 守护进程的是为其他进程服务的,如果被服务的进程,也就是主进程down 或执行完毕了,所有守护进程全部 down.
import threading
import time
class Your_name(threading.Thread):
    def __init__(self,name):
        super(Your_name, self).__init__() # 使用父类的构造函数
        self.name = name
    def run(self): # 这里必须为 run ,重构的父类 run() 函数
        print("your name is %s" % self.name)
        print(threading.current_thread())
        time.sleep(2)
        print("%s 执行完毕" % self.name)
name1 = Your_name("bushaoxun")
name2 = Your_name("shaoxun")
name1.setDaemon(True)  # 设置 name1 为守护线程
name2.setDaemon(True)  # 设置 name2 为守护进程
name1.start()
name2.start()
end_time = time.time()
print("主线程执行完毕")
# 执行结果
your name is bushaoxun
<Your_name(bushaoxun, started daemon 16660)>
your name is shaoxun
<Your_name(shaoxun, started daemon 12796)>
主线程执行完毕   # 程序并没有打印 time.sleep(2) 后的语句,说明主线程执行完毕,程序退出了。 
线程锁(互斥锁Mutex)
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
因为全局解释器锁每执行100条字节码,就释放一次,所以会造成数据的不准确。所以,每个线程访问同一份数据时,需要自己再加一层锁。
import time
import threading
 
def addNum():
    global num #在每个线程中都获取这个全局变量
    print('--get num:',num )
    time.sleep(1)
    lock.acquire() #修改数据前加锁
    num  -=1 #对此公共变量进行-1操作
    lock.release() #修改后释放
 
num = 100  #设定一个共享变量
thread_list = []
lock = threading.Lock() #生成全局锁
for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)
 
for t in thread_list: #等待所有线程执行完毕
    t.join()
 
print('final num:', num )
RLock(递归锁)
就是在一个大锁中还要再包含子锁,但是有一个问题,就是加锁后再释放锁的时候,有可能造成不能释放锁。解决方案就是使用递归锁。
import threading,time
 
def run1():
    print("grab the first part data")
    lock.acquire()
    global num
    num +=1
    lock.release()
    return num
def run2():
    print("grab the second part data")
    lock.acquire()
    global  num2
    num2+=1
    lock.release()
    return num2
def run3():
    lock.acquire()
    res = run1()
    print('--------between run1 and run2-----')
    res2 = run2()
    lock.release()
    print(res,res2)
 
 
if __name__ == '__main__':
 
    num,num2 = 0,0
    lock = threading.RLock()
    for i in range(10):
        t = threading.Thread(target=run3)
        t.start()
 
while threading.active_count() != 1:
    print(threading.active_count())
else:
    print('----all threads done---')
    print(num,num2)
Semaphore(信号量)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据,也就是同时可以运行一定数量的线程,常用在连接池,和socket 线程,
防止过多的线程,导致上下文切换频繁,降低服务器性能
import threading,time
def run(name):
    semaphore.acquire()
    time.sleep(2)
    print("%s is running" % name)
    semaphore.release()
if __name__ == "__main__":
    semaphore = threading.BoundedSemaphore(5)
    for i in range(22):
        t = threading.Thread(target=run,args=("bushaoxun-"+str(i),))
        t.start()
while threading.active_count() != 1:  # 当前活动线程数不等于 1
    time.sleep(2)
    print(threading.active_count())
else:
    print("线程执行完毕")
# 执行结果
bushaoxun-0 is running
bushaoxun-1 is running
bushaoxun-3 is running
bushaoxun-2 is running
bushaoxun-4 is running
18
bushaoxun-5 is running
bushaoxun-6 is running
bushaoxun-9 is running
bushaoxun-8 is running
bushaoxun-7 is running
13
bushaoxun-10 is running
bushaoxun-11 is running
bushaoxun-14 is running
bushaoxun-13 is running
bushaoxun-12 is running
8
bushaoxun-15 is running
bushaoxun-16 is running
bushaoxun-19 is running
bushaoxun-18 is running
bushaoxun-17 is running
3
bushaoxun-20 is running
bushaoxun-21 is running
1
线程执行完毕
Event
Event对象用于线程间通信,它是由线程设置的信号标志,如果信号标志位为假,则线程等待直到信号被其他线程设置成真。
Event对象实现了简单的线程通信机制,它提供了设置信号,清除信号,等待等用于实现线程间的通信。
1.设置信号
使用Event的set()方法可以设置Event对象内部的信号标志为真。Event对象提供了isSet()方法来判断其内部信号标志的状态,当使用event对象的set()方法后,isSet()方法返回真.
2.清除信号
使用Event对象的clear()方法可以清除Event对象内部的信号标志,即将其设为假,当使用Event的clear方法后,isSet()方法返回假
3.等待
Event对象wait的方法只有在内部信号为真的时候才会很快的执行并完成返回。当Event对象的内部信号标志位假时,则wait方法一直等待到其为真时才返回。
可以使用Event让工作线程优雅地退出,示例代码如下:
import threading,time
event = threading.Event()
event.set()
class Sports(threading.Thread):
    def __init__(self):
        super(Sports, self).__init__()
    def run(self):
        while True:
            if event.is_set():
                print("%s is running" % self.getName())
                time.sleep(10)
            else:
                print("%s is stopping" % self.getName())
                break
                print("程序已经退出")
def test():
    t1 = []
    for i in range(6):
        t1.append(Sports())
    for i in t1:
        i.start()
    quit = input("请输入命令退出:")
    if quit == "quit":
        event.clear()
if __name__ == "__main__":
    test()
# 执行结果
Thread-1 is running
Thread-2 is running
Thread-3 is running
Thread-4 is running
Thread-5 is running
Thread-6 is running
请输入命令退出:quit     # 这里只是简单模拟一下
Thread-6 is stopping
Thread-5 is stopping
Thread-2 is stopping
Thread-3 is stopping
Thread-1 is stopping
Thread-4 is stopping
queue
队列是线程间最常用的交换数据的形式。Queue模块是提供队列操作的模块,简单易用。
- class queue.Queue(maxsize=0) #先入先出
- class queue.LifoQueue(maxsize=0) #last in fisrt out
- class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列
import queue
name = queue.Queue(2) # 创建一个先进先出的队列,队列只允许存放两个数据
name.put("bushaoxun") # 往队列中传入数据
name.put("shaoxun")
print(name.full()) # 如果队列满了则显示 True
print(name.qsize())# 查看队列尺寸
print(name.get()) # 从队列中读取数据
print(name.empty())# 如果为空则返回 True
# name.get_nowait() 如果队列为空,会直接报错队列为空,非阻塞
# name.put_nowait() 如果队列满了,会直接告诉你队列满了,非阻塞
# name.q.join() 等待队列为空后,再执行别的操作
# 执行结果
True
2
bushaoxun
False
线程和队列实现生产者和消费者模型
import threading,queue,time
q = queue.Queue()
def Producer(name):
    count = 1
    while count < 10:
        time.sleep(0.4)
        q.put(count)
        print("producer %s has made %s clothes" %(name,count))
        count += 1
def Consumer(name):
    count =1
    while count < 10:
        time.sleep(1)
        if not q.empty():
            data = q.get()
            #print(data)
            print("%s 买了第 %s 件衣服" %(name,data))
        else:
            print("There is no clothes,%s, please go home" % name)
            break
        count += 1
P = threading.Thread(target=Producer,args=("bushaoxun",))
C1 = threading.Thread(target=Consumer,args=("guolijuan",))
C2 = threading.Thread(target=Consumer,args=("buruoxuan",))
P.start()
C1.start()
C2.start()
# 执行结果
producer bushaoxun has made 1 clothes
producer bushaoxun has made 2 clothes
guolijuan 买了第 1 件衣服
buruoxuan 买了第 2 件衣服
producer bushaoxun has made 3 clothes
producer bushaoxun has made 4 clothes
guolijuan 买了第 3 件衣服
buruoxuan 买了第 4 件衣服
producer bushaoxun has made 5 clothes
producer bushaoxun has made 6 clothes
producer bushaoxun has made 7 clothes
guolijuan 买了第 5 件衣服
buruoxuan 买了第 6 件衣服
producer bushaoxun has made 8 clothes
producer bushaoxun has made 9 clothes
guolijuan 买了第 7 件衣服
buruoxuan 买了第 8 件衣服
guolijuan 买了第 9 件衣服
There is no clothes,buruoxuan, please go home
There is no clothes,guolijuan, please go home
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号