多线程基础

1.什么是多任务?

打个比方,你一边在用浏览器上网,一边在听MP3,
一边在用Word赶作业,这就是多任务,同时有3个任务正在运行。
还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。

2.单核CPU是怎么执行多任务的呢?

操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

扩展:

–单核CPU,操作系统轮流让各个任务交替执行,(并发)

–多核CUP, 并行执行多任务只能在多核CPU上实现,(并行)

3.进程:Process

对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。

4.线程:Thread

有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。

5.多任务实现方式:

多进程模式;

多线程模式;

多进程+多线程模式

6.线程小结:

1.线程是最小的执行单元,而进程由至少由一个线程组成。
如何调度进程和线程,完全由操作系统决定,
程序自己不能决定什么时候执行,执行多长时间。

2.多任务 可以由多进程完成,也可以由一个进程内的多线程完成。

3.由于线程是操作系统直接支持的执行单元,因此,高级语言通常
都内置多线程的支持,Python也不例外,并且,Python的线程是
真正的Posix(可移植性操作系统接口)线程,而不是模拟出
来的线程。

4.多线程多用于i/o密集型,多进程多用于计算密集型:

– 计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多
– IO密集型指的是系统的CPU性能相对硬盘、内存要好很多
– IO 就是接口 (input输入就是一种接口)

7.线程的实现模块:

_thread和threading,_thread是低级模块,
threading是高级模块,对_thread进行了封装。
绝大多数情况下,我们只需要使用threading这个高级模块。

1.前言引入:

		-- from time import sleep

		-- def sing():
		--     for i in range(3):
		--         print("正在唱歌...%d"%i)
		--         sleep(1)

		-- def dance():
		--     for i in range(3):
		--         print("正在跳舞...%d"%i)
		--         sleep(1)

		-- if __name__ == '__main__':
		--     sing() #唱歌3次
		--     dance() #跳舞3次

		很显然刚刚的程序并没有完成唱歌和跳舞同时进行的要求

2.(了解)
– 第一种:_thread

例1:

				import _thread
				from time import sleep, ctime

				def loop0():
				    print('loop0开始:', ctime())
				    sleep(2)
				    print('loop0结束:', ctime())

				def loop1():
				    print('loop1开始:', ctime())
				    sleep(1)
				    print('loop1结束:', ctime())

				def main():
				    _thread.start_new_thread(loop0, ())
				    _thread.start_new_thread(loop1, ())
				     
				    sleep(3)  # 防止主线程结束,子线程还在运行

				if __name__ == '__main__':
				    main()

3.第二种:threading

			threading.activeCount(): 返回正在运行的线程数量。
			threading.currentThread(): 返回当前的线程变量。
			

			线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
				run(): 用以表示线程活动的方法。
				start():开启线程。
				join([time]): 设置join之后,主线程等待子线程全部执行完成后
							  或者子线程超时后,主线程才结束。
				isAlive(): 返回线程是否活动的。
				getName(): 返回线程名。
				setName(): 设置线程名。

例1 单线程:

				import time

				def tongZhi():
				    print("同学们,考试要带准考证和身份证")
				    time.sleep(1)

				if __name__ == "__main__":

				    for i in range(5):
				        tongZhi()

例1 多线程:

				import threading
				import time

				def tongZhi():
				    print("同学们,考试要带准考证和身份证")
				    time.sleep(1)

				if __name__ == "__main__":

				    for i in range(5):

				        t = threading.Thread(target=tongZhi)
				        t.start()  # 启动线程,即让线程开始执行

				-- 1.可以明显看出使用了多线程并发的操作,花费时间要短很多
				-- 2.创建好的线程,需要调用start()方法来启动
				-- 3.主线程会等待所有的子线程结束后才结束

4.查看线程数量:threading.activeCount()

		import threading
		from time import sleep,ctime

		def sing():
		    for i in range(3):
		        print("正在唱歌...%d"%i)
		        sleep(1)
		def dance():
		    for i in range(3):
		        print("正在跳舞...%d"%i)
		        sleep(1)

		if __name__ == '__main__':
		    print('---开始---:%s'%ctime())

		    t1 = threading.Thread(target=sing)
		    t2 = threading.Thread(target=dance)

		    t1.start()
		    t2.start()

		    while True:
		    	# 1.返回正在运行的线程数量
		        length = threading.activeCount() 
		        # 返回一个包含正在运行的线程的list
		        # length = len(threading.enumerate())  
		        print('当前运行的线程数为:%d'%length)

		        if length<=1:
		            break

		        sleep(0.5)

5.join的用法:

		import threading
		import time

		def action(arg):
		    time.sleep(1)
		    print  ('the thread name is:%s' % threading.currentThread().getName())
		    print ('the arg is:%s' %arg)
		    time.sleep(1)


		thread_list = []    #线程存放列表
		
		for i in range(4):
		    t =threading.Thread(target=action,args=(i,))
		    thread_list.append(t)

		for t in thread_list:
		    t.start()
		    # t.join()  # 错误用法,每个线程都被上一个线程的join阻塞

		for t in thread_list:
		    t.join()    # 告诉父线程,等着子线程
		    


-- 解释:t1.setDaemon(True)的操作,将父线程设置为了守护线程。
-- 根据setDaemon()方法的含义,父线程打印内容后便结束了,不管子线程是否执行完毕了。

-- 这基本和join是相反的


import threading
import time

class MyThread(threading.Thread):
        def __init__(self,id):
                threading.Thread.__init__(self)
        def run(self):
                time.sleep(5)
                print("777777 ")
 

if __name__ == "__main__":
        t1=MyThread(999)
        t1.setDaemon(True)
        t1.start()
        print("I am the father thread.")

6.run()和start() 方法的区别:(了解,演示run()和start())

	-- 	1.用start()方法来启动线程,真正实现了多线程运行
	-- 	2.run()方法只是类的一个普通方法而已,如果直接调用Run方法,
	-- 	  程序中依然只有主线程这一个线程。

	-- 		import threading
			
	-- 		class myThread(threading.Thread):
	-- 		    def __init__(self, threadID, name, counter):
	-- 		        threading.Thread.__init__(self)
	-- 		        self.threadID = threadID
	-- 		        self.name = name
	-- 		        self.counter = counter
			 
	-- 		    def run(self):
	-- 		        currentTreadname = threading.currentThread()
	-- 		        print ("running in", currentTreadname)
			 
	-- 		thread = myThread(1,"mythread",1)

	-- 		thread.run()
	-- 		thread.start()		




9.线程优缺点:

多线程优点:
多线程类似于同时执行多个不同程序,多线程运行有如下优点:
1、使用线程可以把占据 耗时的任务 放到后台去处理。

2、用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件。
的处理,可以弹出一个进度条来显示处理的进度。

3、程序的运行速度可能加快。

4、在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程
就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。

多线程缺点:
1、开启大量线程消耗资源。
2、线程之间数据是共享的,容易出现数据混乱的情况,加锁麻烦。

10.出现全局变量混乱的问题:

假设两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算,
t1和t2都各对g_num加1百万次,g_num的最终的结果应该为2百万。

但是由于是多线程同时操作,有可能出现下面情况:

1.在g_num=0时,t1取得g_num=0。此时系统把t1调度为”waiting”状态,
把t2转换为”running”状态,t2也获得g_num=0。

2.然后t2对得到的值进行加1并赋给g_num,使得g_num=1。

3.然后系统又把t2调度为”waiting”,把t1转为”running”。
线程t1又把它之前得到的0加1后赋值给g_num。

4.这样导致虽然t1和t2都对num加1,但结果仍然是g_num=1。

例1:

		import threading
		import time

		g_num = 0

		def work1(num):
		    global g_num

		    for i in range(num):
		        g_num += 1
		    print("----in work1, num is %d---" % g_num)


		def work2(num):
		    global g_num

		    for i in range(num):
		        g_num += 1
		    print("----in work2, num is %d---" % g_num)


		print("---线程创建之前g_num is %d---" % g_num)

		t1 = threading.Thread(target=work1, args=(100,))  # 改为1000000
		t1.start()
		-- t1.join()

		t2 = threading.Thread(target=work2, args=(100,))  # 改为1000000
		t2.start()
		-- t2.join()

		# 功能相当于join,等待子线程结束
		while threading.activeCount() != 1:
		    time.sleep(1)

		print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)

11.解决全局变量混乱的问题:(使用线程锁,排队上洗手间)

	import threading
	import time

	g_num = 0

	def test1(num):
	    global g_num

	    for i in range(num):
	        # True表示堵塞 即如果这个锁在上锁之前已经被上锁了,那么这个线程会在这里一直等待到解锁为止
	        # False表示非堵塞,即不管本次调用能够成功上锁,都不会卡在这,而是继续执行下面的代码
	        mutexFlag = mutex.acquire(True)

	        if mutexFlag:  # 如果上锁成功
	            g_num += 1

	            mutex.release()  # 解锁

	    print("---test1---num=%d"%g_num)

	def test2(num):
	    global g_num
	    for i in range(num):

	        mutexFlag = mutex.acquire(True) # True表示堵塞

	        if mutexFlag:
	            g_num += 1

	            mutex.release()

	    print("---test2---num=%d"%g_num)

	#创建一个互斥锁
	#这个所默认是未上锁的状态
	mutex = threading.Lock()

	p1 = threading.Thread(target=test1, args=(1000000,))
	p1.start()

	p2 = threading.Thread(target=test2, args=(1000000,))
	p2.start()

	# 功能相当于join,等待子线程结束
	while threading.activeCount() != 1:
	    time.sleep(1)

	print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)

11.全局解释器锁:GIL

1.多核CPU:(引言,了解)
如果你拥有一个多核CPU,你肯定在想,多核应该可以同时执行多个线程。

如果写一个死循环的话,会出现什么情况呢?
打开Mac OS X的Activity Monitor,或者Windows的Task Manager,都可以监控某个进程的CPU使用率。
我们可以监控到一个死循环线程会100%占用一个CPU。
如果有两个死循环线程,在多核CPU中,可以监控到会占用200%的CPU,也就是占用两个CPU核心。
要想把N核CPU的核心全部跑满,就必须启动N个死循环线程。

试试用Python写个死循环:

				import threading, multiprocessing
				from multiprocessing import Process

				def loop():
				    x = 0
				    while True:
				        x = x ^ 1  # 按位异或运算符:当两对应的二进位相异时,结果为1

				# 此时计算密集型,多线程起不到作用
				for i in range(multiprocessing.cpu_count()):
				    t = threading.Thread(target=loop)
				    t.start()

启动与CPU核心数量相同的N个线程,在4核CPU上可以监控到CPU占用率仅有100%,也就是仅使用了一核。
但是用C、C++或Java来改写相同的死循环,直接可以把全部核心跑满,4核就跑到400%,8核就跑到800%,为什么Python不行呢?

2.因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:
Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,
然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。
这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,
即使100个线程跑在100核CPU上,也只能用到1个核。

3.GIL是Python解释器设计的历史遗留问题,通常我们用的解释器是官方实现的CPython,
要真正利用多核,除非重写一个不带GIL的解释器。
所以,在Python中,可以使用多线程,但不要指望能有效利用多核。
如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。
不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,
但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

12总结:

线程定义。
线程的方法。
线程优缺点。
Lock()
GIL

13(了解) 死锁: --就是互相等待,程序

	#coding=utf-8
	import threading
	import time

	class MyThread1(threading.Thread):
	    def run(self):
	        if mutexA.acquire():
	            print(self.name+'----do1---up----')
	            time.sleep(1)

	            if mutexB.acquire():   
	                print(self.name+'----do1---down----')

	                mutexB.release()
	            mutexA.release()    


	class MyThread2(threading.Thread):
	    def run(self):
	        if mutexB.acquire():
	            print(self.name+'----do2---up----')
	            time.sleep(1)

	            if mutexA.acquire():   
	                print(self.name+'----do2---down----')

	                mutexA.release()
	            mutexB.release()   


	mutexA = threading.Lock()
	mutexB = threading.Lock()

	if __name__ == '__main__':
	    t1 = MyThread1()
	    t2 = MyThread2()
	    t1.start()
	    t2.start()

14.将类中的函数放在线程中执行,且能控制线程的结束

	import time
	import threading
	
	class DownThread:
	    def __init__(self):
	        self._running = True

	    def terminate(self):
	        self._running = False

	    def run(self, n):
	        while self._running and n > 0:
	            print('T-minus', n)
	            n -= 1
	            time.sleep(1)


	if __name__ == '__main__':
	    c = DownThread()
	    t = threading.Thread(target=c.run, args=(10,))
	    t.start()
	    time.sleep(3)
	    c.terminate()
	    t.join()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值