python-网络编程 (个人学习笔记1.1)--python中的多线程(threading模块)

一、多线程提要

什么是线程:

线程:就是操作系统能够进行运算调度的最小单位

1.什么是多线程

多线程:原本需要按先后执行的两个程序,在同一时刻同时进行,每个在同时执行的程序,称为一个线程
在实际运行中,因为CPU同一时间只能执行一项运算,这时CPU就会将两个程序的执行进行拆分,即:每过极其短暂的一段时间,交替执行不同的两个程序,从视觉上,给人一种同时进行的感觉。

2.python多线程须知:

(1) python多线程弊端

1.在python 中因为GIL(全局解释器锁)的存在,多线程运算中,python仅会将一个线程调入解释器进行操作,即:仅会调用CPU的一个核进行数据操作,即使计算机的CPU有两个,四个,甚至八个以上多核,依然如此。可以理解为,python并不能像java等等语言一样,在多线程中,同时调用多核,实现更加完善的多线程。

2.受单核操作的影响,python在多线程执行计算密集型代码(例如同时执行两个计算量十分庞大的方法)时,通常执行效率在理论上和单线程是并驾齐驱的,甚至因为每次需要暂停记录一次程序状态,让线程能在下一次交替后成功继续向下执行,产生的多余消耗,使得python在多线程执行计算密集型代码时,执行效率甚至不如单线程操作(这个问题在python3.5版本之前尤为明显,多线程执行相同计算密集型代码时(例如1加到1亿),运行效率比起单线程运行,差时甚至能多上一倍时间。当然,现版本python已将这一问题进行了许多的优化。)

(2)python多线程利用

1.相比多线程执行计算密集型代码,python在多线程运算I/O密集型代码时,依然是十分有效的

2.可以使用多进程简单解决多线程弊端问题

python官方原话:
However, threading is still an appropriate model if you want to run multiple I/O-bound tasks simultaneously.

二、多线程(threading模块)

1.threading:

代码案例中使用过的方法列表:

import threading		#导入模块

threading.Thread(target = <方法名>, args = (<方法参数>,))	#创建threading对象

.setdaemon	#守护线程

.start()	#开始线程

.join()		#阻塞线程直到线程执行完毕

代码案例:

import threading
import time

start = time.time()
def ret(n):
    num = 0
    for i in range(n):
        num+=i
    print(num)

def bet(n):
    num = 0
    for i in range(n):
        num+=i
    print(num)

t1 = threading.Thread(target=ret,args=(100000000,))		#创建多线程对象,target=ret(传入指定方法)  args=(100000000,)    (给指定方法传入参数,需要以元祖形式存放)
t2 = threading.Thread(target=bet,args=(100000000,))

#t1.setDaemon()  #setDaemon:守护进程,被守护进程将不会被等待,即如果其他进程已经提前执行结束,那么被守护进程将被中断
t1.start()			#开始进程
t2.start()

#t1.join()				#阻塞线程直到线程执行完毕
#让两个线程加入到主线程中,使得当两个线程都执行完毕后,再执行主线程中的print('这就是多线程'),否则主线程程序将和两个支线程同时执行
#t2.join()
#print('这就是多线程')

end = time.time()

print(end-start)		#打印总执行时间

2.类创建多线程对象

利用继承threading.Thread类,来自主使用类创建多线程对象。

import threading
import time

start = time.time()
class MyThread(threading.Thread):
    def __init__(self,num):		
        threading.Thread.__init__(self)	#
        self.num = num

    def run(self):			#该方法继承至threading.Thread,重写后,创建对象,方法将自动执行run方法
        num1 = 0
        for i in range(self.num):
            num1+=i
        print(num1)

if __name__ == '__main__':
    t1 = MyThread(1000)
    t2 = MyThread(5000)

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    end = time.time()
    print(end-start)

3.threading.Lock() 为线程加锁

代码案例方法运用列表:

threading.Lock()  #创建锁对象

.acquire()	#设置锁

.release() #释放锁

代码案例:

import threading
num = 100
def run():

    r.acquire()		#开头:使得当前字段在多线程时,依然只能串行执行,即单线程执行
    global num
    print('...')
    temp = num
    num = temp-1
    r.release()		#结尾:释放锁,即:解锁


r = threading.Lock()	# 创建一个锁的对象
for i in range(100):
    t1 = threading.Thread(target=run)
    t1.start()

t1.join()
print(num)

4.死锁现象(该现象需要避免,解析在下方)

import threading
import time

class Mythread(threading.Thread):
    def setAB(self):
        lockA.acquire()
        print('这是锁A...')
        time.sleep(3)
        lockB.acquire()
        print('这是锁B...')
        lockB.release()
        lockA.release()

    def setBA(self):
        lockB.acquire()
        print('这是锁B...')
        time.sleep(5)
        lockA.acquire()
        print('这是锁A...')
        lockA.release()
        lockB.release()

    def run(self):
        self.setAB()
        self.setBA()

if __name__ == '__main__':

    lockA = threading.Lock()
    lockB = threading.Lock()

    thread = []

    for i in range(5):
        thread.append(Mythread())

    for t in thread:
        t.start()
    for t in thread:
        t.join()

输出:

这是锁A...
这是锁B...
这是锁B...
这是锁A...		#此时程序卡死,不再继续执行

解析上方代码:当线程开始执行时,多线程同时调用run方法,然后执行setAB,setBA方法,因为setAB,setBA中的time.sleep方法存在,两者的调用存在时间差,即存在某个时刻,一个线程在调用setAB方法时,另一个线程正在进行setBA。
setAB,先创建A锁,再创建B锁,释放B锁,再释放A锁
setBA,先创建B锁,再创建A锁,释放A锁,再释放B锁
当setAB与setBA同时执行时,就会发生,setAB需要B锁时,setBA正在使用B锁,setBA需要A锁时,setAB正在使用A锁,此时因为无法执行下方的释放语句,程序将卡主,无法继续向下执行,这就是死锁现象

5.解决死锁现象的递归锁

代码案例方法运用列表:

threading.RLock()	#创建递归锁对象

代码案例:

import threading
import time

class Mythread(threading.Thread):
    def setAB(self):
        lock.acquire()
        print('这是锁A...')
        time.sleep(3)
        lock.acquire()
        print('这是锁B...')
        lock.release()
        lock.release()

    def setBA(self):
        lock.acquire()
        print('这是锁B...')
        time.sleep(5)
        lock.acquire()
        print('这是锁A...')
        lock.release()
        lock.release()

    def run(self):
        self.setAB()
        self.setBA()

if __name__ == '__main__':

    lock = threading.RLock()		#threading.RLock() 递归锁

    thread = []

    for i in range(5):
        thread.append(Mythread())

    for t in thread:
        t.start()
    for t in thread:
        t.join()

递归锁:相当于是在一个锁上增加了一个计数器,在一个线程中,每次调用递归锁(lock.acquire()),不释放(lock.release())时,计数器从0向上加一,每次释放锁,计数器减一,当计数器为0时,其他线程可继续使用该锁。

输出:

这是锁A...
这是锁B...
这是锁B...
这是锁A...
这是锁A...
这是锁B...
这是锁B...
这是锁A...
这是锁A...
这是锁B...
这是锁B...
这是锁A...
这是锁A...
这是锁B...
这是锁B...
这是锁A...
这是锁A...
这是锁B...
这是锁B...
这是锁A...

Process finished with exit code 0

6.为什么要使用锁

当多个线程需要同时调用一个数据时,可能发生源数据使用不当的现象。
例如:
A有500块钱,B有800块钱,此时A向B转账100块,B刚好也想转账给A200块,正常情况下,应该发生,A减掉100块,B减掉200块后,A400块再拿到B的200块,B600块,再拿到A的100块。最后A为600块,B为700块。
但是如果多线程操作情况下,可能发生,某两个执行转账转入的线程,同时拿到了还没减去转出金额的A的500块,B的800块,此时还没减去转出的钱,就已经加上了后来转来的100块和200块。
即使后来执行了转出减掉了双方多出的非正常余额的语句,但是从逻辑上来说,这是不正确的,转账仅仅只是一个案例,放在其他地方时,程序就可能报错,此时就可以给语句加上线程锁,例如给转出,转入加上锁,使得转出,转入只能串行(单线程)执行,避免了可能同时发生的,转出转入造成的,根数据异常的情况

7.信号量(信号锁)

threading.BoundedSemaphore():信号量,可以限制每次执行的线程的个数

案例代码方法运用列表:

threading.BoundedSemaphore()	#创建信号量对象,限制线程个数

.acquire()	#创建信号量

.release()	#释放信号量

代码案例:

import threading,time

class Mythread(threading.Thread):

    def run(self):
        if semaphore.acquire():
            print(self.name)
            time.sleep(2)
            semaphore.release()

if __name__=="__main__":
    semaphore=threading.BoundedSemaphore(3)	#限制每次3个线程
    threads=[]

    for i in range(9):
        threads.append(Mythread())

    for t in threads:
        t.start()

输出:

Thread-1
Thread-2
Thread-3		#每两秒打印三个线程名称
Thread-4
Thread-6
Thread-5
Thread-7
Thread-8
Thread-9

8.条件变量

代码案例方法运用列表:

threading.Condition()	#创建条件变量对象

wait()		#条件不满足时调用,线程会释放锁并进入等待状态

notify()		#条件创造后调用,通知wait()所在语句激活一个线程

notifyAll()		#条件创造后调用,通知wait()所在语句激活所有线程
import threading
from random import randint
import time

class robots(threading.Thread):

    def run(self):
        global save_work
        while True:
            num = randint(0,100)
            print('机器人:',self.name,'举起了靶子:',str(num),save_work)
            if con_lock.acquire():
                save_work.append(num)
                con_lock.notify()
                con_lock.release()
            time.sleep(3)


class shooter(threading.Thread):

    def run(self):
        global save_work
        while True:
            con_lock.acquire()
            if not save_work:
                con_lock.wait()
            print('射手:', self.name, '打掉了' + str(save_work[0]) + '号靶子', save_work)
            del save_work[0]
            con_lock.release()
            time.sleep(0.3)

if __name__ == '__main__':

    save_work = []
    con_lock = threading.Condition()
    threads = []

    for i in range(5):
        threads.append(robots())

    for i in range(2):
        threads.append(shooter())

    for t in threads:
        t.start()
    for t in threads:
        t.join()

两个射手射击靶子,每3秒有5个机器人举起靶子。

9.事件发生

代码案例方法运用列表:

threading.Event()	#创建事件对象

event.isSet()  	#返回event的状态值

event.wait()	#如果 event.isSet()==False将堵塞线程

event.set()  	#设置event的状态值为True,所有weit()状态的线程激活

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

代码案例:

import threading
import time

class Boss(threading.Thread):
    def run(self):
        print("老板:今天给请大伙聚餐!")
        event.isSet() or event.set()
        time.sleep(4)
        print("聚餐结束......")
        print('老板:刚好趁咱们兴头正盛,回去加班到十一点吧!')
        event.isSet() or event.set()

class Worker(threading.Thread):
    def run(self):
        event.wait()
        print("员工%s:year!!!"%(self.name))
        time.sleep(0.25)
        event.clear()
        event.wait()
        print("员工%s:No!!!"%(self.name))

if __name__=="__main__":

    event=threading.Event()
    threads=[]

    for i in range(5):
        threads.append(Worker())

    threads.append(Boss())

    for t in threads:
        t.start()
    for t in threads:
        t.join()

输出:

老板:今天给请大伙聚餐!
员工Thread-4:year!!!
员工Thread-3:year!!!
员工Thread-1:year!!!
员工Thread-5:year!!!员工Thread-2:year!!!

聚餐结束......
老板:刚好趁咱们兴头正盛,回去加班到十一点吧!
员工Thread-2:No!!!员工Thread-4:No!!!
员工Thread-1:No!!!
员工Thread-5:No!!!

员工Thread-3:No!!!

Process finished with exit code 0

10.队列

队列:一种数据结构,特性是先进先出,先存入队列的数据,当需要取出数据时,先存入的先取出来

内置方法:

q.qsize() #返回队列的大小

q.empty() #如果队列为空,返回True,反之False

q.full() #如果队列满了,返回True,反之False

q.full 与 maxsize 大小对应

q.get([block[, timeout]]) #获取队列,timeout等待时间

q.get_nowait() #相当q.get(False)

非阻塞 q.put(item) #写入队列,timeout等待时间

q.put_nowait(item) #相当q.put(item, False)

q.task_done() #在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号

q.join() #实际上意味着等到队列为空,再执行别的操作

例子:

import threading
from random import randint
import time
import queue

class robots(threading.Thread):

    def run(self):
        global save_work
        while True:
            num = randint(0,100)
            print('机器人:',self.name,'举起了靶子:',str(num))
            save_work.put(num)
            time.sleep(2)


class shooter(threading.Thread):

    def run(self):
        global save_work
        while True:
            print('射手:', self.name, '打掉了' + str(save_work.get()) + '号靶子')
            time.sleep(0.3)

if __name__ == '__main__':

    save_work = queue.Queue()
    threads = []

    for i in range(5):
        threads.append(robots())

    for i in range(2):
        threads.append(shooter())

    for t in threads:
        t.start()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值