8-2-3python语法基础-并发编程-进程和线程对比学习:进程冲突和锁,线程冲突和锁

前言

进程和线程,有很多地方非常类似,包括使用的方法也很多相同的,
所以我决定放到一起对比学习,
这一篇,专门对比:
进程冲突和锁,
线程冲突和锁

线程冲突

from threading import Thread, Lock
import time, os

lock = Lock()


def foo(i):
    print('%s: %s is running' % (i, os.getpid()))
    time.sleep(2)
    print('%s:%s is done' % (i, os.getpid()))


tread_list = []
for i in range(1, 5):
    t = Thread(target=foo, args=(i,))
    t.start()
    tread_list.append(t)

for t in tread_list:
    t.join()

输出结果:
1: 22024 is running
2: 22024 is running
3: 22024 is running
4: 22024 is running
1:22024 is done
3:22024 is done
4:22024 is done
2:22024 is done

从输出的结果来看,多个线程同时在操作,如果是对同一个文件读写操作,很明显已经乱套了,这并不是我们想要的;如果多线程在读写同一文件时想要保证数据安全,必然需要加上互斥锁 Lock,例如下面这个 demo

使用同步锁(互斥锁Lock)解决线程冲突

from threading import Thread, Lock
import time, os

lock = Lock()


def foo(i):
    lock.acquire()
    print('%s: %s is running' % (i, os.getpid()))
    time.sleep(2)
    print('%s:%s is done' % (i, os.getpid()))
    lock.release()


tread_list = []
for i in range(1, 5):
    t = Thread(target=foo, args=(i,))
    t.start()
    tread_list.append(t)

for t in tread_list:
    t.join()

输出结果:
1: 22030 is running
1:22030 is done
2: 22030 is running
2:22030 is done
3: 22030 is running
3:22030 is done
4: 22030 is running
4:22030 is done

这样加锁了之后,即便是对同一个文件进行读写操作,线程使用互斥锁 Lock 之后也不会造成数据混乱的问题

使用with的方式进行加锁

Lock 对象和 with 语句块一起使用可以保证互斥执行,就是每次只有一个线程可以执行 with 语句包含的代码块。with 语句会在这个代码块执行前自动获取锁,在执行结束后自动释放锁。

这种方式更加的优雅,也避免了忘记释放锁

from threading import Thread, Lock
import time, os

lock = Lock()


def foo(i):
    with lock:
        print('%s: %s is running' % (i, os.getpid()))
        time.sleep(2)
        print('%s:%s is done' % (i, os.getpid()))



tread_list = []
for i in range(1, 5):
    t = Thread(target=foo, args=(i,))
    t.start()
    tread_list.append(t)

for t in tread_list:
    t.join()

为什么会出现冲突?

  • 为什么会出现这种冲突的情况,就是因为多个线程同时操作一个资源的时候就会出现这个问题,
  • 但是有GIL锁啊,为什么还会产生这种情况?
  • 还是因为时间片轮转,你取到了数据,还没有来得及操作,这个值就被其他的线程拿走了,
    线程调度本质上是不确定的,因此,在多线程程序中错误地使用锁机制可能会导致随机数据损坏或者其他的异常行为,我们称之为竞争条件。为了避免竞争条件,最好只在临界区(对临界资源进行操作的那部分代码)使用锁。

加锁和join的区别

  • 加锁是把锁住的这一段代码变成了同步运行,变成了串行了,
  • 所以加锁实际是牺牲了效率,性能,来保证线程的安全性,
  • 互斥锁和join的区别:在线程启动阶段就加入join实现串行也能避免线程冲突,
  • 但是join明显这种效率太低,很明显是加锁的效率更高.

什么时候加锁?

加锁、解锁(同步/互斥)是多线程中非常基本的操作,但不少的代码对它们处理的很不好。
比如:
一是加锁范围太大,虽然避免了逻辑错误,但锁了不该锁的东西,难免降低程序的效率;
二是该锁的不锁,导致各种莫名其妙的错误;

要正确的运用锁操作,首先要弄清楚什么时候需要加锁。一般可能“同时发生多个写操作”或“同时发生读写操作”时,必需要加Lock.

如果操作的是批量发送邮件,批量发送短信,这样的操作,使用多线程不会有加锁的问题,
因为不涉及到操作同一个数据资源,
但是如果是操作同一个数据,比如操作同一个文件,操作同一个全局变量,操作同一个数据库的数据,
这样的地方就要加锁,涉及到一个资源竞争的问题,

线程死锁(需要获取多个锁的情况)

防止死锁的加锁机制

  • 问题
    你正在写一个多线程程序,其中线程需要一次获取多个锁,此时如何避免死锁问题。

  • 解决方案
    在多线程程序中,死锁问题很大一部分是由于线程同时获取多个锁造成的。举个例子:一个线程获取了第一个锁,然后在获取第二个锁的 时候发生阻塞,那么这个线程就可能阻塞其他线程的执行,从而导致整个程序假死。

体验死锁的现象:

from threading import Thread, Lock
import time

boy_lock = Lock()
girl_lock = Lock()


class Boy(Thread):

    def run(self):
        if boy_lock.acquire():  # 锁住线程,
            print("boy say ...")
            time.sleep(3)
            if girl_lock.acquire():
                print("girl say sorry")
                girl_lock.release()

            boy_lock.release()  # 释放锁,


class Girl(Thread):

    def run(self):
        if girl_lock.acquire():  # 锁住线程,
            print("girl say ...")
            time.sleep(3)
            if boy_lock.acquire():
                print("boy say sorry")
                boy_lock.release()

            girl_lock.release()


boy = Boy()
boy.start()
girl = Girl()
girl.start()

使用递归锁RLock解决单线程死锁现象

  • 单线程解决死锁,是因为使用不当导致的,可以使用Rlock避免死锁,
  • 递归锁,来解决死锁的问题,你可以看做是钥匙串上面的两把钥匙,
  • 一旦你拿到了一把钥匙,就证明你拿到了整个钥匙串了,
from threading import Thread, Lock, RLock
import time

boy_lock = girl_lock = RLock()


class Boy(Thread):

    def run(self):
        if boy_lock.acquire():  # 锁住线程,
            print("boy say ...")
            time.sleep(3)
            if girl_lock.acquire():
                print("girl say sorry")
                girl_lock.release()

            boy_lock.release()  # 释放锁,


class Girl(Thread):

    def run(self):
        if girl_lock.acquire():  # 锁住线程,
            print("girl say ...")
            time.sleep(3)
            if boy_lock.acquire():
                print("boy say sorry")
                boy_lock.release()

            girl_lock.release()


boy = Boy()
boy.start()
girl = Girl()
girl.start()

这样你什么都不用改,只需要把两个锁定义为递归锁,这两个就变成了一个钥匙串,就可以解决死锁的问题了,

进程冲突

from multiprocessing import Process, Lock
import time, os


def foo(i):
    print('%s: %s is running' % (i, os.getpid()))
    time.sleep(2)
    print('%s:%s is done' % (i, os.getpid()))


if __name__ == '__main__':
    p_list = []
    for i in range(1, 5):
        t = Process(target=foo, args=(i,))
        t.start()
        p_list.append(t)

    for t in p_list:
        t.join()
    print("程序结束")

输出结果:
1: 22083 is running
4: 22086 is running
3: 22085 is running
2: 22084 is running
1:22083 is done
3:22085 is done
4:22086 is done
2:22084 is done
程序结束

同样的,如果这个进程操作同一个文件也会乱套,

加进程锁解决进程同步

from multiprocessing import Process, Lock
import time, os


def foo(i, lock):
    lock.acquire()
    print('%s: %s is running' % (i, os.getpid()))
    time.sleep(2)
    print('%s:%s is done' % (i, os.getpid()))
    lock.release()


if __name__ == '__main__':
    p_list = []
    lock = Lock()
    for i in range(1, 5):
        t = Process(target=foo, args=(i, lock))
        t.start()
        p_list.append(t)

    for t in p_list:
        t.join()
    print("程序结束")

输出结果:
1: 22093 is running
1:22093 is done
2: 22094 is running
2:22094 is done
4: 22096 is running
4:22096 is done
3: 22095 is running
3:22095 is done
程序结束

注意加锁的方式,和线程有点不一样,
至于进程死锁,进程的递归锁,就不详细展开了,和线程的使用是一样的,

所以我才把进程和线程的锁相关内容,放到一起,对比学习,

posted @ 2022-10-02 16:38  技术改变命运Andy  阅读(1016)  评论(0编辑  收藏  举报