python多线程死循环_Python多线程之死锁

1.什么是死锁?

死锁是由于两个或以上的线程互相持有对方需要的资源,且都不释放占有的资源,导致这些线程处于等待状态,程序无法执行。

2.产生死锁的四个必要条件

1.互斥性:线程对资源的占有是排他性的,一个资源只能被一个线程占有,直到释放。

2.请求和保持条件:一个线程对请求被占有资源发生阻塞时,对已经获得的资源不释放。

3.不剥夺:一个线程在释放资源之前,其他的线程无法剥夺占用。

4.循环等待:发生死锁时,线程进入死循环,永久阻塞。

3.产生死锁的原因

在多线程的场景,比如线程A持有独占锁资源a,并尝试去获取独占锁资源b同时,线程B持有独占锁资源b,并尝试去获取独占锁资源a。

这样线程A和线程B相互持有对方需要的锁,从而发生阻塞,最终变为死锁。

造成死锁的原因可以概括成三句话:

1.不同线程同时占用自己锁资源

2.这些线程都需要对方的锁资源

3.这些线程都不放弃自己拥有的资源

1477786-20200528194027906-330614095.jpg

线程A持有锁资源a的同时,线程B也持有了锁资源b。

线程A想要继续执行需要锁资源b,线程B想要继续执行需要锁资源a

线程A不释放锁资源a,线程B不释放锁资源b

线程A线程B都需要a,b两把锁,如果我们加锁的顺序一致(线程A先拿a加锁,再拿b加锁,再解锁b,然后解锁a,线程B同理),就不会出现死锁的情况。

4.三种典型的死锁

常见的3种死锁的类型:静态的锁顺序死锁,动态的锁顺序死锁,协作对象之间的死锁。

静态的锁顺序死锁

a和b两个方法都需要获得A锁和B锁。一个线程执行a方法且已经获得了A锁,在等待B锁;另一个线程执行了b方法且已经获得了B锁,在等待A锁。这种状态,就是发生了静态的锁顺序死锁。

静态是指,在程序中,对于某个锁来说加锁和解锁的位置是不变的。

我们用Python直观的演示一下静态的锁顺序死锁。

假设银行系统中,用户a试图转账100块给用户b,与此同时用户b试图转账200块给用户a,则可能产生死锁。

2个线程互相等待对方的锁,互相占用着资源不释放。

#coding=utf-8

importtimeimportthreadingclassAccount:def __init__(self, _id, balance):

self.id=_id

self.balance=balancedefwithdraw(self, amount):

self.balance-=amountdefdeposit(self, amount):

self.balance+=amountdeftransfera_b(_from, to, amount):

lock_a.acquire()#锁住自己的账户

time.sleep(1) #让交易时间变长,2个交易线程时间上重叠,有足够时间来产生死锁

_from.withdraw(amount)print('wait for lock_b')

lock_b.acquire()#锁住对方的账户

to.deposit(amount)

lock_b.release()

lock_a.release()deftransferb_a(_from, to, amount):

lock_b.acquire()#锁住自己的账户

time.sleep(1) #让交易时间变长,2个交易线程时间上重叠,有足够时间来产生死锁

_from.withdraw(amount)print('wait for lock_a')

lock_a.acquire()#锁住对方的账户

to.deposit(amount)

lock_a.release()

lock_b.release()

lock_a=threading.Lock()

lock_b=threading.Lock()

a= Account('a', 1000)

b= Account('b', 1000)#a往b转账100

t1 = threading.Thread(target=transfera_b, args=(a, b, 100))

t1.start()#b往a转账200

t2 = threading.Thread(target=transferb_a, args=(b, a, 200))

t2.start()

t1.join()

t2.join()print("a的账户余额:",a.balance)print("b的账户余额:",b.balance)

动态的锁顺序死锁

动态的锁顺序死锁是指两个线程调用同一个方法时,传入的参数颠倒造成的死锁。如下代码,一个线程调用了transfer方法并传入参数a,b,100;另一个线程调用了transfer方法并传入参数b,a,200。此时就可能发生在静态的锁顺序死锁中存在的问题,即:第一个线程获得了a的锁并等待b的锁,第二个线程获得了b的锁锁并等待a的锁。

这里动态是指,某个锁会根据参数的传递,在不同的位置加锁和解锁。

我们这里还是转账的例子:

#coding=utf-8

importtimeimportthreadingclassAccount:def __init__(self, _id, balance):

self.id=_id

self.balance=balance

self.lock=threading.Lock()defwithdraw(self, amount):

self.balance-=amountdefdeposit(self, amount):

self.balance+=amountdeftransfer(_from,to, amount):

_from.lock.acquire()#锁住自己的账户

time.sleep(1) #让交易时间变长,2个交易线程时间上重叠,有足够时间来产生死锁

_from.withdraw(amount)print('wait for lock')

to.lock.acquire()#锁住对方的账户

to.deposit(amount)

to.lock.release()

_from.lock.release()

a= Account('a', 1000)

b= Account('b', 1000)#a往b转账100

t1 = threading.Thread(target=transfer, args=(a, b, 100))

t1.start()#b往a转账200

t2 = threading.Thread(target=transfer, args=(b, a, 200))

t2.start()

t1.join()

t2.join()print("a的账户余额:",a.balance)print("b的账户余额:",b.balance)

协作对象之间的死锁

如果在持有锁时调用某个外部方法,那么将可能出现死锁问题。在这个外部方法中可能会获得其他锁,或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。

为了避免这种危险的情况发生,我们使用开放调用。如果调用某个外部方法时不需要持有锁,我们称之为开放调用。

5.避免死锁的方法(重点)

避免死锁可以概括成三种方法:

锁顺序操作的死锁:

解决静态的锁顺序死锁的方法:所有需要多个锁的线程,都要以相同的顺序来获得锁。

ContractedBlock.gif

ExpandedBlockStart.gif

#coding=utf-8

importtimeimportthreadingclassAccount:def __init__(self, _id, balance):

self.id=_id

self.balance=balancedefwithdraw(self, amount):

self.balance-=amountdefdeposit(self, amount):

self.balance+=amountdeftransfera_b(_from, to, amount):

lock_a.acquire()#锁住自己的账户

time.sleep(1) #让交易时间变长,2个交易线程时间上重叠,有足够时间来产生死锁

_from.withdraw(amount)

lock_b.acquire()#锁住对方的账户

to.deposit(amount)

lock_b.release()

lock_a.release()deftransferb_a(_from, to, amount):

lock_a.acquire()#锁住自己的账户

time.sleep(1) #让交易时间变长,2个交易线程时间上重叠,有足够时间来产生死锁

_from.withdraw(amount)

lock_b.acquire()#锁住对方的账户

to.deposit(amount)

lock_b.release()

lock_a.release()

lock_a=threading.Lock()

lock_b=threading.Lock()

a= Account('a', 1000)

b= Account('b', 1000)#a往b转账100

t1 = threading.Thread(target=transfera_b, args=(a, b, 100))

t1.start()#b往a转账200

t2 = threading.Thread(target=transferb_a, args=(b, a, 200))

t2.start()

t1.join()

t2.join()print("a的账户余额:",a.balance)print("b的账户余额:",b.balance)

View Code

解决动态的锁顺序死锁的方法:比较传入锁对象的哈希值,根据哈希值的大小来确保所有的线程都以相同的顺序获得锁 。

ContractedBlock.gif

ExpandedBlockStart.gif

#coding=utf-8

importthreadingimporthashlibclassAccount:def __init__(self, _id, balance):

self.id=_id

self.balance=balance

self.lock=threading.Lock()defwithdraw(self, amount):

self.balance-=amountdefdeposit(self, amount):

self.balance+=amountdeftransfer(_from, to, amount):

hasha,hashb=hashlock(_from, to)if hasha >hashb:

_from.lock.acquire()#锁住自己的账户

to.lock.acquire() #锁住对方的账户

#交易#################

_from.withdraw(amount)

to.deposit(amount)#################

to.lock.release()

_from.lock.release()elif hasha

to.lock.acquire()#锁住自己的账户

_from.lock.acquire() #锁住对方的账户

#交易#################

_from.withdraw(amount)

to.deposit(amount)#################

_from.lock.release()

to.lock.release()else: ##hash值相等,最上层使用mylock锁,你可以把transfer做成一个类,此类中实例一个mylock。

mylock.acquire()

_from.lock.acquire()#锁住自己的账户

to.lock.acquire() #锁住对方的账户

#交易#################

_from.withdraw(amount)

to.deposit(amount)#################

to.lock.release()

_from.lock.release()

mylock.release()defhashlock(_from,to):

hash1=hashlib.md5()

hash1.update(bytes(_from.id, encoding='utf-8'))

hasha=hash1.hexdigest()

hash=hashlib.md5()

hash.update(bytes(to.id, encoding='utf-8'))

hashb=hash.hexdigest()returnhasha,hashb

a= Account('a', 1000)

b= Account('b', 1000)

mylock=threading.Lock()#a往b转账100

t1 = threading.Thread(target=transfer, args=(a, b, 100))

t1.start()#b往a转账200

t2 = threading.Thread(target=transfer, args=(b, a, 200))

t2.start()

t1.join()

t2.join()print("a的账户余额:",a.balance)print("b的账户余额:",b.balance)

View Code

python中使用上下文管理器来解决动态的锁顺序死锁问题,当然还是固定锁的顺序操作:Python中死锁的形成示例及死锁情况的防止

解决方案是为程序中的每一个锁分配一个唯一的id,然后只允许按照升序规则来使用多个锁,这个规则使用上下文管理器 是非常容易实现的,示例如下:

importthreadingfrom contextlib importcontextmanager#Thread-local state to stored information on locks already acquired

_local =threading.local()

@contextmanagerdef acquire(*locks):#Sort locks by object identifier

locks = sorted(locks, key=lambdax: id(x))#Make sure lock order of previously acquired locks is not violated

acquired = getattr(_local,'acquired',[])if acquired and max(id(lock) for lock in acquired) >=id(locks[0]):raise RuntimeError('Lock Order Violation')#Acquire all of the locks

acquired.extend(locks)

_local.acquired=acquiredtry:for lock inlocks:

lock.acquire()yield

finally:#Release locks in reverse order of acquisition

for lock inreversed(locks):

lock.release()del acquired[-len(locks):]

如何使用这个上下文管理器呢?你可以按照正常途径创建一个锁对象,但不论是单个锁还是多个锁中都使用 acquire() 函数来申请锁, 示例如下:

importthreading

x_lock=threading.Lock()

y_lock=threading.Lock()defthread_1():whileTrue:

with acquire(x_lock, y_lock):print('Thread-1')defthread_2():whileTrue:

with acquire(y_lock, x_lock):print('Thread-2')

t1= threading.Thread(target=thread_1)

t1.daemon=True

t1.start()

t2= threading.Thread(target=thread_2)

t2.daemon=True

t2.start()

如果你执行这段代码,你会发现它即使在不同的函数中以不同的顺序获取锁也没有发生死锁。 其关键在于,在第一段代码中,我们对这些锁进行了排序。通过排序,使得不管用户以什么样的顺序来请求锁,这些锁都会按照固定的顺序被获取。

开放调用(针对对象之间协作造成的死锁):

解决协作对象之间发生的死锁:需要使用开放调用,即避免在持有锁的情况下调用外部的方法,就是尽量将锁的范围缩小,将同步代码块仅用于保护那些设计共享状态的操作。

使用定时锁-:

加上一个超时时间,若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。

但是如果有非常多的线程同一时间去竞争同一批资源,就算有超时和回退机制,还是可能会导致这些线程重复地尝试但却始终得不到锁。

6.死锁检测

死锁检测:死锁检测即每当一个线程获得了锁,会在线程和锁相关的数据结构中( map 、 graph 等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。

其中,死锁检测最出名的算法是由艾兹格·迪杰斯特拉在 1965 年设计的银行家算法,通过记录系统中的资源向量、最大需求矩阵、分配矩阵、需求矩阵,以保证系统只在安全状态下进行资源分配,由此来避免死锁。

1477786-20200528204844624-337336654.jpg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Python 是一种解释型编程语言,对于循环语句的执行效率不高是其缺陷之一。但是,可以使用 Python多线程技术,加速 for 循环执行效率,提高程序的运行效率。 多线程技术把一个程序分为多个小的独立的线程,每个线程可以独立地执行不同的任务。对于循环语句,可以使用多线程技术,把循环任务分成多个线程,每个线程执行一部分循环任务,最后把所有线程执行的结果汇总起来,大大提高了程序的运行效率。 在 Python 实现多线程的方式有多种,其最常用的是使用 threading 模块。可以使用 threading.Thread() 方法创建一个线程对象,并传递需要执行的函数作为参数,多个线程对象可以同时启动,从而实现多线程并发执行的效果。 以上就是使用 Python 多线程技术加速 for 循环的基本方法,需要注意的是,多线程技术不能减少 CPU 的负载,而是通过增加并发执行线程数,提高程序的运行效率。在使用多线程技术时,需要避免共享变量的竞争和死锁等问题,同时合理控制线程数,防止过度占用 CPU 资源。 ### 回答2: Python是一种解释性语言,直接执行Python代码时,所有代码都是在同一个线程运行的。因此,如果要执行计算量比较大的任务,常规的单线程方式可能耗费大量的时间,影响程序的运行效率。 为了提高Python程序的执行效率,可以使用多线程技术。多线程是指一个进程内同时运行多个线程,每个线程执行不同的任务,从而提高程序的计算效率。在Python,使用threading模块可以轻松实现多线程编程。 下面以计算圆周率为例,演示如何使用Python多线程加速for循环: ``` python import threading def calculate_pi(start, end): """计算圆周率""" pi = 0 for i in range(start, end): pi += 4 * (-1) ** i / (2 * i + 1) return pi if __name__ == "__main__": num_threads = 4 # 设置线程数 threads = [] n = 10000000 interval = n // num_threads for i in range(num_threads): start = i * interval end = (i + 1) * interval if i < num_threads - 1 else n t = threading.Thread(target=calculate_pi, args=(start, end)) threads.append(t) t.start() result = sum([t.join() for t in threads]) print(result) ``` 在上面的代码,首先定义了一个calculate_pi函数,用于计算圆周率。该函数有两个参数,start和end,用于指定计算的范围。然后,在主函数,定义了num_threads参数,表示要使用的线程数。接着,通过一个for循环,将计算圆周率的任务分配给多个线程。每个线程接收两个参数,即start和end,然后启动线程并将其添加到线程列表。最后,使用sum函数将所有线程的计算结果相加,得出最终结果并打印输出。 通过上面的代码可以看出,使用Python多线程技术可以有效地加速for循环的执行,节省计算时间,提高程序的效率。同时,需要注意的是,在多线程编程,要注意线程的同步和互斥问题,避免出现数据竞争等问题,从而保证程序的正确性和稳定性。 ### 回答3: 在Python,我们可以使用多线程来加速for循环。for循环是Python最常用的循环结构之一,但是当需要处理大量数据时,for循环变得非常耗时,导致程序运行速度变慢。这时候,我们可以使用多线程来分担任务,从而提高程序的运行速度。 首先,我们需要导入Python多线程库——threading。然后,我们可以创建多个线程,并将for循环的任务分配给这些线程来处理。具体实现如下: ``` python import threading def process_data(data): # 进行数据处理操作 pass def handle_thread(data, start, end): for i in range(start, end): process_data(data[i]) def run_threads(data, num_threads): thread_list = [] data_length = len(data) for i in range(num_threads): start = int(data_length * i / num_threads) end = int(data_length * (i + 1) / num_threads) t = threading.Thread(target=handle_thread, args=(data, start, end)) thread_list.append(t) t.start() for t in thread_list: t.join() if __name__ == '__main__': data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] num_threads = 2 # 设置线程数量为2 run_threads(data, num_threads) ``` 在上面的代码,我们使用了三个函数:process_data()、handle_thread()和run_threads()。process_data()函数用于处理数据,handle_thread()函数是线程处理函数,它负责执行for循环的任务。run_threads()函数用于创建线程并分配任务。 我们首先在主函数定义数据和线程数量,然后调用run_threads()函数开启多个线程。run_threads()函数根据线程数量将任务分配给不同的线程,每个线程分配的任务不同。线程执行完成后,主函数将继续执行。最后,我们需要调用join()函数来等待所有线程执行完毕。 通过使用多线程,我们可以大大提高程序的处理能力,使得for循环的执行更加高效。但是,需要注意的是,在使用多线程时,由于数据访问冲突的问题,我们需要使用线程锁来解决。同时,多线程带来一些问题,例如线程间通信、线程切换耗时等,需要谨慎使用和注意细节。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值