线程

1.多任务(多个东西同时进行)

1.1说明

  1. 单核CPU看着的“一起”执行为但执行——时间片轮转,更新、切换足够快看上去就为同时在运行
  2. 并行:真的多任务 任务数 <= CPU核数
  3. 并发:假的多任务 任务数 >= CPU核数 (优先级调动)一般为并发事件、假的多任务
  4. 线程是实现多任务的一种方式

1.2线程——python的threading模块

  1. python的threading模块是对thead做了一些包装,让使用更加方便
  2. 使用threading模块
    单线程执行:一个程序运行后一定有一个主线程,子线程由主线程创建
    函数名()——调用函数 函数名————告诉这个函数 = :为创建对象
  3. 如果主线程没有代码,主线程等着子线程先结束。等子线程都结束,主线程就结束,当主线程结束,程序运行结束
  4. 查看线程数threading模块的enumerate
    想知道当前程序运行起来后有多少线程,每个线程是什么信息,调用enumerat(),返回值是一个列表,列表有几个元素就有几个线程
  5. 线程没有先后运行顺序,操作系统先调动谁谁就执行
  6. 如果创建的thread时指定的函数,运行结束那么意味着这个子线程结束。如果子线程没有结束,主线程先结束,那么程序结束
  7. 一个线程的开始由调用start()开始,当带调用Thread的时候,不会创建线程,当带调用Thread创建出来的实例对象的start方法的时候才会创建线程以及让这个线程开始运行

1.2.1通过继承Thread类完成创建线程

  1. 线程代码的封装Thread实际上是创建一个对象,实际上创建线程是调用start的时候
  2. python的threading.Thread类有 一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。多线程执行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import threading  
    import time
    def saySorry():
    print("I'm sory!")
    time.sleep(1)

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

说明

  1. 可以明显看出使用了多线程并发的操作,花费时间要短很多
  2. 当调用start()时,才会真正创建线程,并且开始执行

####1.2.2线程代码的封装
通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承threading. Thread就可以了,然后重写run方法
示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#coding=utf-8
import threading
import time
class MyThread(threading. Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i) #name属性中保存的是当前线程的名字print(msg)


if __name__ == '__main_' :
t = MyThread() # Thread(target=函数名)
t.start()

target更简单
**run与start()方法的区别** run() 方法并不启动一个新线程,就是在主线程中调用了一个普通函数而已。 start() 方法是启动一个子线程,线程名就是自己定义的name。 因此,如果你想启动多线程,就必须使用start()方法。

###1.3多线程共享全局变量

1. 在一个函数中对全局变量进行修改的时候,到底是否需安使用globai进行说明要看是否对全局变量的执行指向进行了修改,如果修改了执行,即让全局变量指向了一个新的地方,那么必须使用global如果,仅仅是修改了指向的空间中的数据,此时不用必须使用global
●如果在函数中需要修改全局变量,需要使用global进行申明——global+变量名 ●注意:为了区分全局变量和局部变量通常在全局变量前添加 g_ 或 gl_名

为什么要共享?
因为多任务往往配合使用

2.多线程共享存在资源竞争关系
解决办法:
原子性——
要么不做,要么做完

3.同步的概念
同步就是协同步调,按预定的先后次序进行运行.如:你说完,我再说

‘同”字从字面上容易理解为一起动作

其实不是,”同”字应是指协同,协助.互相配合,

如进程,线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行,B执行,再将结果给AA再继续操作,

  1. 解决线程同时修改全局变量的方式
    对于上一小节提出的那个计算错误的问题,可以通过线程同步来进行
    解决思路,如下:
    1). 系统调用t1,然后获取到g_num的值为,此时上一把锁,即不允许其他线程操作g_num
    2).t1对g_num的值进行+1
    3).t1解锁,此时g_num的值为1,其他的线程就可以使用g_snum了,而且是g_num的值不是0而是1
    4).同理其他线程在对g_num进行修改时,都要先上锁,必理完后再解锁,在上锁的整个过程中不允许其他线程访问,就保证了数据的正确性

####1.4锁

  1. 互斥锁
    当多个线程几乎同时修改某一个共未享数据的时候!需要进行同步控制

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

threading模块中定义了LOCK类,可以方便的处理锁定:

# 创建锁
mutex = threading.LOCK()

# 锁定
mutex.acpuire()

# 释放
mutex.release()

###注意:
●如果这个所之前是没有上锁的,那么acquire不会堵塞
●如果在调用acquire对这个所上锁之前他就已经被其他线程上了锁,那么此时acquire就会堵塞,直到这个锁被解锁为止
####1.死锁
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁
尽管死锁很少发生,但一旦发生就会造成应用的停止响应.
####2.避免死锁
●程设计时尽量避免(银行家算法)
●添加超时时间等

附录-银行家算法

[背景知识]

一个银行家如何将一定数目的资金安全地借给若干个客户,使这些客户既能借到钱完成要干的事,同时银行家又能收回全部资金而不至于破产,这就是银行家问题。这个问题同操作系统中资源分配问题十分相似:银行家就像- -个操作系统,客户就像运行的进程,银行家的资金就是系统的资源。

[问题的描述]

一个银行家拥有一定数量的资金,有若干个客户要贷款。每个客户须在一开始就声明他所需贷款的总额。若该客户贷款总额不超过银行家的资金总数,银行家可以接收客户的要求。客户贷款是以每次一个资金单位(如1万RMB等)的方式进行的,客户在借满所需的全部单位款额之前可能会等待,但银行家须保证这种等待是有限的,可完成的。

例如:有三个客户C1,C2,C3,向银行家借款,该银行家的资金总额为10个资金单位,其中C1客户要借9各资金单位, C2客户要借3个资金单位,C3客户要借8个资金单位,总计20个资金单位。某一时刻的状态
解决:同时放贷给c1用户借2各单位,给c2借2各单位,给c3借四个单位,再给c2借1各单位,这时才借款完成,当收完c2后支付c3剩下的部分,当c3还款后再支付给c1余款。

结论:当多个事件同时发生时,需一个一个解决,一个解锁后,另一个才能继续进行。