多任务编程之线程Thread

多任务编程

(1)意义

  • 可以充分利用计算机资源,提高程序运行效率

(2)定义

  • 通过应用程序利用计算机的多个核心达到同时执行多个任务的目的,以此来提升程序的执行效率

(3)实施方案

  • 多进程、多线程

(4)并行

  • 多个计算机核心在同时处理多个任务,这多个任务间是并行关系

(5)并发

  • 同时处理多个任务,在任务间不断的切换,达到好像都在处理运行的效果

1. 线程

(1)定义

  • 线程是一种多任务编程方式,可以使用计算机资源。线程被称为轻量级的进程

(2)特征

  1. 线程是计算机多核分配的最小单位(进程是操作系统资源分配的最小单位)
  2. 一个进程可以包含多个线程
  3. 线程也是一个运行过程,也要消耗计算机资源,多个线程共享进程的资源和空间
  4. 线程也拥有自己所特有的资源属性,比如指令集,TID等
  5. 线程无论是创建、删除还是运行,消耗的资源都小于进程
  6. 利用多个线程中的进程号,可以证明其同属于一个进程(os.getpid

(3)线程创建流程

  1. 创建线程对象
import threading

# 此时并没有真正创建线程
threading.Thread( )
  • 参数:
  • name 线程名称,默认为Thread -1,依次增加Thread -2 …
  • target 线程函数
  • args 元组,给线程函数位置传参
  • kwargs 字典,给线程函数以键值传参
  1. 启动创建线程
# 此时才正真创建线程
t.start()
  1. 回收线程
t.join([timeout])

(4)线程对象的属性

  1. t.is_alive() 查看线程的状态
  2. t.neme 线程名称
  3. t.setName() 设置线程名称
  4. threading.currentThread() 获取当前线程对象
  5. threading.enumerat() 获取所有线程对象
    在这里插入图片描述
  6. 默认情况下,如果主线程死亡,所有子线程也会死去,所以需要设置t.daemon 属性,使主线程结束不会影响到分支线程的执行。
设置方法
t.daemon = True
或
t.setDaemon(True)
判断daemon属性:
t.isDaemon( )
  1. 注意点

        线程daemon属性的设置在start之前
        一般设置daemon属性后不再使用join

2. 使用threading模块

(1)单线程执行示例

import time

def say_hello():
	print("hello python")
	time.sleep(1)
	print('**************')
	
if __name__ == "__main__":
	for i in range(5):
		say_hello()		

运行结果

ubantu@ubantu-virtual-machine:~/Thread_model$ python3 01_thread.py 
hello python
**************
hello python
**************
hello python
**************
hello python
**************
hello python
**************

(2)多线程执行示例

import threading
import time

def saySorry():
    print("hello python")
    time.sleep(1)

if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target=saySorry)
        t.start()  # 启动线程,即让线程开始执行

执行结果

ubantu@ubantu-virtual-machine:~/Thread_model$ python3 02_thread.py 
hello python
hello python
hello python
hello python
hello python
************
************
************
************
************

(3)说明

  • 使用多线程时,一次性输出5条hello python信息,然后再一次性输出5条星杠,而单线程时时一条一条输出的。
  • 可以明显看出使用了多线程并发的操作,花费时间要短很多
  • 当调用start()时,才会真正的创建线程,并且开始执行

2. 主线程会等待所有的子线程结束后才结束

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()

    # sleep(5)  # 屏蔽此行代码,试试看,程序是否会立马结束?
    print('---结束---:%s'% ctime())

3. 查看线程数

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:
    	# enumerate():获取当前所有线程
        length = len(threading.enumerate())
        print('当前运行的线程数为:%d'% length)
        # 当程序中只剩下主线程时,让程序结束
        if length<=1:
            break

        sleep(0.5)

运行结果

ubantu@ubantu-virtual-machine:~/Thread_model$ python3 04_thread.py 
---开始---:Mon Apr  1 11:13:46 2019
正在唱歌...0
正在跳舞...0
当前运行的线程数为:3
当前运行的线程数为:3
正在跳舞...1
正在唱歌...1
当前运行的线程数为:3
当前运行的线程数为:3
正在跳舞...2
正在唱歌...2
当前运行的线程数为:3
当前运行的线程数为:3
当前运行的线程数为:1

4. 继承Thread创建自己的线程类

(1)流程

  1. 继承Thread类
  2. 运行Thread类中的__init__方法,以获取父类原有的属性
  3. 重写run方法

(2)代码示例

import threading
import time

class MyThread(threading.Thread):
	def __init__(self, target, name='', args=(), kwargs={}):
		super().__init__()
		self.name = name
		self.target = target
		self.args = args
		self.kwargs = kwargs
	
	def run(self):
		self.target(*self.args)

def palyer(song, times, sec):
	for i in range(times):
		print('playig %s : %s' % (song, time.ctime()))
		time.sleep(sec)

if __name__ == "__main__":
	t_list = []
	t = MyThread(target=palyer, args=('爱我中华', 10, 1))
	t.start()
	t_list.append(t)
	t2 = MyThread(target=palyer, args=('一起摇摆', 5, 1))
	t2.start()
	t_list.append(t2)
	for i in t_list:
		i.join()

运行结果

ubantu@ubantu-virtual-machine:~/Thread_model$ python3 MyThread.py 
playig 爱我中华 : Mon Apr  1 13:38:12 2019
playig 一起摇摆 : Mon Apr  1 13:38:12 2019
playig 爱我中华 : Mon Apr  1 13:38:13 2019
playig 一起摇摆 : Mon Apr  1 13:38:13 2019
playig 一起摇摆 : Mon Apr  1 13:38:14 2019
playig 爱我中华 : Mon Apr  1 13:38:14 2019
playig 一起摇摆 : Mon Apr  1 13:38:15 2019
playig 爱我中华 : Mon Apr  1 13:38:15 2019
playig 一起摇摆 : Mon Apr  1 13:38:16 2019
playig 爱我中华 : Mon Apr  1 13:38:16 2019
playig 爱我中华 : Mon Apr  1 13:38:17 2019
playig 爱我中华 : Mon Apr  1 13:38:18 2019
playig 爱我中华 : Mon Apr  1 13:38:19 2019
playig 爱我中华 : Mon Apr  1 13:38:20 2019
playig 爱我中华 : Mon Apr  1 13:38:21 2019

5. 多线程-共享全局变量

import threading
import time

glob_num = 100

def fun1():
	global glob_num
	glob_num += 10
	print("-----in fun1 glob_num = %d -----" % glob_num)

def fun2():
	print("-----in fun2 glob_num = %d -----" % glob_num)

def main():
	t1 = threading.Thread(target=fun1)
	t2 = threading.Thread(target=fun2)
	t1.start()
	# 保证进程1先执行,改变全局变量值,
	time.sleep(0.1)
	# 在进程2中获取改变后的值
	t2.start()
	time.sleep(0.1)

	print("-----in main Thread blob_num = %d-----" % glob_num)

if __name__ == "__main__":
	main()

运行结果

ubantu@ubantu-virtual-machine:~/Thread_model$ python3 test_thread.py 
-----in fun1 glob_num = 110 -----
-----in fun2 glob_num = 110 -----
-----in main Thread blob_num = 110-----
  • 可以看出当一个进程对全局变量进行修改后,其他所有进程也会拿到修改后的值。

6. 当多个线程共同操作全局变量时,可能引发错误

import threading
import time

glob_num = 0

def fun1(num):
	global glob_num
	for i in range(num):
		glob_num += 1
	print("-----in fun1 glob_num = %d -----" % glob_num)

def fun2(num):
	global glob_num
	for i in range(num):
		glob_num += 1
	print("-----in fun2 glob_num = %d -----" % glob_num)

def main():
	t1 = threading.Thread(target=fun1, args=(1000000,))
	t2 = threading.Thread(target=fun2, args=(1000000,))

	t1.start()
	t2.start()
	
	# 等待上面程序执行完毕
	time.sleep(2)

	print("-----in main Thread blob_num = %d-----" % glob_num)

if __name__ == "__main__":
	main()

运行结果

ubantu@ubantu-virtual-machine:~/Thread_model$ python3 test_thread.py 
-----in fun2 glob_num = 1091668 -----
-----in fun1 glob_num = 1192062 -----
-----in main Thread blob_num = 1192062-----
ubantu@ubantu-virtual-machine:~/Thread_model$ python3 test_thread.py 
-----in fun1 glob_num = 1312078 -----
-----in fun2 glob_num = 1446422 -----
-----in main Thread blob_num = 1446422-----
  • 原因:假设当全局变量等于1000000时开始,此时线程1和线程2同时拿到了该对象1000000,此时线程1对其进行了+1,变为10000001,然后线程2又对其进行+1,此时该变量变为10000001,即产生了资源争夺,数据覆盖,就导致了最终执行结果不是2000000。
  • 为了避免这种情况,我们就需要用到线程锁,保证线程之间不会产生资源争夺,谁先拿到锁就先执行,执行完毕后释放锁,其他线程才能对其操作。

7. 解决线程同时修改全局变量的方式

(1)互斥锁

  • 当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
  • 线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁
  • 某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性
import threading
import time

glob_num = 0

def fun1(num):
	global glob_num
	# 上锁
	lock.acquire()
	for i in range(num):
		glob_num += 1
	# 解锁
	lock.release()
	print("-----in fun1 glob_num = %d -----" % glob_num)

def fun2(num):
	global glob_num
	lock.acquire()
	for i in range(num):
		glob_num += 1
	lock.release()
	print("-----in fun2 glob_num = %d -----" % glob_num)

lock=threading.Lock()

def main():
	t1 = threading.Thread(target=fun1, args=(1000000,))
	t2 = threading.Thread(target=fun2, args=(1000000,))

	t1.start()
	t2.start()
	
	# 等待上面程序执行完毕
	time.sleep(2)

	print("-----in main Thread blob_num = %d-----" % glob_num)

if __name__ == "__main__":
	main()

运行结果

ubantu@ubantu-virtual-machine:~/Thread_model$ python3 test_thread.py 
-----in fun1 glob_num = 1000000 -----
-----in fun2 glob_num = 2000000 -----
-----in main Thread blob_num = 2000000-----
ubantu@ubantu-virtual-machine:~/Thread_model$ python3 test_thread.py 
-----in fun1 glob_num = 1000000 -----
-----in fun2 glob_num = 2000000 -----
-----in main Thread blob_num = 2000000-----
ubantu@ubantu-virtual-machine:~/Thread_model$ 

优化:上锁原则,锁的代码越少越好

import threading
import time

glob_num = 0

def fun1(num):
	global glob_num
	for i in range(num):
		lock.acquire()
		glob_num += 1
		lock.release()
	print("-----in fun1 glob_num = %d -----" % glob_num)

def fun2(num):
	global glob_num
	for i in range(num):
		lock.acquire()
		glob_num += 1
		lock.release()
	print("-----in fun2 glob_num = %d -----" % glob_num)

lock=threading.Lock()

def main():
	t1 = threading.Thread(target=fun1, args=(1000000,))
	t2 = threading.Thread(target=fun2, args=(1000000,))

	t1.start()
	t2.start()
	
	# 等待上面程序执行完毕
	time.sleep(2)

	print("-----in main Thread blob_num = %d-----" % glob_num)

if __name__ == "__main__":
	main()

运行结果

ubantu@ubantu-virtual-machine:~/Thread_model$ python3 test_thread.py 
-----in fun1 glob_num = 1859438 -----
-----in fun2 glob_num = 2000000 -----
-----in main Thread blob_num = 2000000-----
ubantu@ubantu-virtual-machine:~/Thread_model$ python3 test_thread.py 
-----in fun2 glob_num = 1970448 -----
-----in fun1 glob_num = 2000000 -----
-----in main Thread blob_num = 2000000-----

(2)死锁

  • 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁
import threading
import time

# 假设此函数属于线程2
def foo():
    lockA.acquire()
    print('func foo ClockA lock')
    time.sleep(0.1)
    lockB.acquire()
    print('func foo ClockB lock')
    lockB.release()
    lockA.release()

# 假设此函数属于线程1
def bar():
	# lockB上锁
    lockB.acquire()
    print('func bar ClockB lock')
    time.sleep(2)  
    # 模拟io或者其他操作,第一个线程执行到这,在这个时候,lockA会被第二个线程占用
    # 所以第一个线程无法进行后续操作,只能等待lockA锁的释放
    lockA.acquire()
    print('func bar ClockA lock')
    lockB.release()
    lockA.release()

def run():
    foo()
    bar()

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

for i in range(10):
    t = threading.Thread(target=run,args=())
    t.start()

运行结果

ubantu@ubantu-virtual-machine:~/Thread_model$ python3 test_thread.py 
func foo ClockA lock
func foo ClockB lock
func bar ClockB lock
func foo ClockA lock





(3)递归锁:(重要)解决死锁

import threading
import time


def foo():
    rlock.acquire()
    print('func foo ClockA lock')
    rlock.acquire()
    print('func foo ClockB lock')
    rlock.release()
    rlock.release()

def bar():
    rlock.acquire()
    print('func bar ClockB lock')
    time.sleep(2)
    rlock.acquire()
    print('func bar ClockA lock')
    rlock.release()
    rlock.release()


def run():
    foo()
    bar()

rlock = threading.RLock() # RLock本身有一个计数器,如果碰到acquire,那么计数器+1
                        # 如果计数器大于0,那么其他线程无法查收,如果碰到release,计数器-1

for i in range(10):
    t = threading.Thread(target=run,args=())
    t.start()

运行结果

ubantu@ubantu-virtual-machine:~/Thread_model$ python3 test_thread.py 
func foo ClockA lock
func foo ClockB lock
func bar ClockB lock
func bar ClockA lock
func foo ClockA lock
func foo ClockB lock
func bar ClockB lock
func bar ClockA lock
func foo ClockA lock
func foo ClockB lock
func bar ClockB lock
func bar ClockA lock
func foo ClockA lock
func foo ClockB lock
func bar ClockB lock
func bar ClockA lock
func foo ClockA lock
func foo ClockB lock
func bar ClockB lock
func bar ClockA lock
func foo ClockA lock
func foo ClockB lock
func bar ClockB lock
func bar ClockA lock
func foo ClockA lock
func foo ClockB lock
func bar ClockB lock
func bar ClockA lock
func foo ClockA lock
func foo ClockB lock
func bar ClockB lock
func bar ClockA lock
func foo ClockA lock
func foo ClockB lock
func bar ClockB lock
func bar ClockA lock
func foo ClockA lock
func foo ClockB lock
func bar ClockB lock
func bar ClockA lock
ubantu@ubantu-virtual-machine:~/Thread_model$ 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值