Python 线程/互斥锁/死锁/线程与进程对比

本文介绍了Python中的线程和进程,包括线程的创建与使用,线程间共享全局变量可能引发的问题,通过互斥锁实现线程同步以避免数据错误,以及死锁的概念。同时,对比了进程和线程的关系、区别、优缺点,强调多进程在稳定性上的优势和多线程在资源开销上的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、介绍
在Python中,想要实现多任务除了使用进程,还可以使用线程来完成,线程是实现多任务的另外一种方式。

概念:线程是进程中执行代码的一个分支,每个执行分支(线程)要想工作执行代码需要cpu进行调度,也就是说线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。

程序启动默认会有一个主线程,程序猿自己创建的线程可以成为子线程,多线程可以完成多任务。

1.导入线程模块

import threading

2.线程类Thread参数说明
Thread([group[, target[, name[, args[, kwargs]]]]])
group:线程组,目前只能使用None
target:执行的目标任务名
args:以元祖方式给执行任务传参
kwargs:以字典方式给执行任务传参
name:线程名,一般不用设置

3.启动线程
start方法

二、线程实例
1.多线程的使用

import threading
import time

def sing():
    #获取当前线程
    current_thread = threading.current_thread()
    print('sing', current_thread)
    for i in range(3):
        print('sing...')
        time.sleep(0.2)

def dance():
    #获取当前线程
    current_thread = threading.current_thread()
    print('dance', current_thread)
    for i in range(3):
        print('dance...')
        time.sleep(0.2)


if __name__ == '__main__':
    # 获取当前线程
    current_thread = threading.current_thread()
    print('main_thread', current_thread)

    #创建子线程
    sing_thread = threading.Thread(target=sing, name='sing_thread')
    dance_thread = threading.Thread(target=dance, name='dance_thread')
    #启动子线程
    sing_thread.start()
    dance_thread.start()

#运行结果
main_thread <_MainThread(MainThread, started 21476)>
sing <Thread(sing_thread, started 14472)>
sing...
dance <Thread(dance_thread, started 22648)>
dance...
dance...
sing...
dance...
sing...

2.线程执行带参数

import threading

def show_info(name, age):
    print(name, age)

if __name__ == '__main__':

    #以元祖方式传参,元祖元素要和函数参数顺序保持一致
    sub_thread = threading.Thread(target=show_info, args={'张三', 30})
    sub_thread.start()

    #字典方式传参,字典的key要和函数里面的参数名保持一致,没有顺序要求
    sub_thread = threading.Thread(target=show_info, kwargs={'age':20, 'name':'张三'})
    sub_thread.start()

3.线程的注意点
3.1线程之间执行是无序的
3.2主线程会等所有的子线程执行结束再结束
3.3线程之间共享全局变量
3.4线程之间共享全局变量出现错误问题

3.1线程之间执行是无序的
线程之间执行是无序的,具体哪个线程执行是由cpu调度决定的

import threading
import time

def task():
    time.sleep(1)
    #获取当前线程
    print(threading.current_thread())

if __name__ == '__main__':
    #循环大量线程,测试线程之间执行是否无序
    for i in range(10):
        #每循环一次创建一个子线程
        sub_thread = threading.Thread(target=task)
        #启动子线程对应的任务
        sub_thread.start()

#运行结果
<Thread(Thread-10, started 22012)>
<Thread(Thread-4, started 7972)>
<Thread(Thread-3, started 22896)>
<Thread(Thread-2, started 23120)>
<Thread(Thread-6, started 21988)>
<Thread(Thread-1, started 21148)>
<Thread(Thread-7, started 23268)>
<Thread(Thread-9, started 19640)>
<Thread(Thread-8, started 1884)>
...

3.2主线程会等所有的子线程执行结束再结束

import threading
import time

def task():
    while True:
        print('任务执行中...')
        time.sleep(0.3)

if __name__ == '__main__':
    #创建子线程
    sub_thread = threading.Thread(target=task)
    sub_thread.start()
    #住线程延迟执行1s
    time.sleep(1)
    print('over')
    exit()

#运行结果 死循环
任务执行中...
任务执行中...
任务执行中...
任务执行中...
over
任务执行中...
任务执行中...
任务执行中...
...

daemon=True 表示创建的子线程守护主线程,主线程退出子线程直接销毁

import threading
import time

def task():
    while True:
        print('任务执行中...')
        time.sleep(0.3)

if __name__ == '__main__':
    #创建子线程
    #daemon=True 表示创建的子线程守护主线程,主线程退出子线程直接销毁
    sub_thread = threading.Thread(target=task, daemon=True)
    sub_thread.start()
    #住线程延迟执行1s
    time.sleep(1)
    print('over')
    #exit()

#运行结果
任务执行中...
任务执行中...
任务执行中...
任务执行中...
over

3.3线程之间共享全局变量

import threading
import time

g_list = list()

def add_data():
    for i in range(3):
        g_list.append(i)
        print('add', i )
        time.sleep(0.3)
    print('添加数据完成', g_list)

def read_data():
    print('read', g_list)

if __name__ == '__main__':
    add_thread = threading.Thread(target=add_data)
    read_thread = threading.Thread(target=read_data)
    add_thread.start()
    #让当前线程(主线程)等待添加数据的子线程执行完成以后代码再继续执行
    add_thread.join()
    read_thread.start()

#运行结果
add 0
add 1
add 2
添加数据完成 [0, 1, 2]
read [0, 1, 2]

3.4线程之间共享全局变量出现错误问题
例子:定义两个函数循环加1,执行100w次

import threading

g_num = 0

def sun_num1():
    for i in range(1000000):
        global g_num
        g_num += 1
    print('sun_num1', g_num)


def sun_num2():
    for i in range(1000000):
        global g_num
        g_num += 1
    print('sun_num2', g_num)

if __name__ == '__main__':
    sub_thread1 = threading.Thread(target=sun_num1)
    sub_thread1.start()
    sub_thread2 = threading.Thread(target=sun_num2)
    sub_thread2.start()

#运行结果
sun_num1 1551061
sun_num2 1452940

import threading

g_num = 0

def sun_num1():
    for i in range(1000000):
        global g_num
        g_num += 1
    print('sun_num1', g_num)


def sun_num2():
    for i in range(1000000):
        global g_num
        g_num += 1
    print('sun_num2', g_num)

if __name__ == '__main__':
    sub_thread1 = threading.Thread(target=sun_num1)
    sub_thread2 = threading.Thread(target=sun_num2)
    sub_thread1.start()
    # 让当前线程(主线程)等待添加数据的子线程执行完成以后代码再继续执行
    sub_thread1.join()
    sub_thread2.start()

#运行结果
sun_num1 1000000
sun_num2 2000000


三、线程同步
保证同一时刻只能有一个线程去操作全局变量:协同步调,按预定的先后次序进行运行。

方式:
1.线程等待(join) – 看上面例子
2.互斥锁:能够保证多个线程去访问共享数据不会出现数据错误问题
互斥锁就是多个线程一起去抢,抢到锁的线程先执行,没有抢到的要等待,等互斥锁使用完释放后,其他等待的线程再去抢这个锁

2.1互斥锁的使用
threading模块中定义了一个Lock变量,本质上是一个函数,通过调用这个函数可以获取一把互斥锁。

#创建锁
mutex = threading.Lock()
#上锁
mutex.acquire()
#这里编写一段代码能保证同一时刻只能有一个线程去操作,对共享数据锁定
#释放锁
mutex.release()

注意点:
1.acquire和 release方法之间的代码同一时刻只能有一个线程去操作
2.如果在在用acquire方式的时候,其他线程已经使用了这个互斥锁,那么此时的acquire方法会堵塞,直到这个互斥锁释放后才再次上线。

import threading

g_num = 0

#创建全局互斥锁
lock = threading.Lock()
def sun_num1():
    #上锁
    lock.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1
    print('sun_num1', g_num)
    #释放锁
    lock.release()


def sun_num2():
    # 上锁
    lock.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1
    print('sun_num2', g_num)
    # 释放锁
    lock.release()


if __name__ == '__main__':
    sub_thread1 = threading.Thread(target=sun_num1)
    sub_thread2 = threading.Thread(target=sun_num2)
    sub_thread1.start()
    sub_thread2.start()


#运行结果
sun_num1 1000000
sun_num2 2000000

互斥锁可以保证同一时刻只有一个线程去执行代码,能够保证全局变量的数据没有问题;
线程等待和互斥锁都是把多任务改成单任务去执行,保证了数据的准确性,但是执行性能会下降。

四、死锁
一直等待对方释放锁的情景就是死锁。

import threading

#创建互斥锁
lock = threading.Lock()

#需求:多线程同时根据下标在列表中取值,要保证同一时刻只能有一个线程去取值

def get_value(index):
    #上锁
    lock.acquire()
    list = [1,2,4]
    #判断下标是否越界
    if index >= len(list):
        print('下标越界',index)
        return
    #根据下标取值
    value = list[index]
    print(value)

    #释放锁
    lock.release()

if __name__ == '__main__':
    #创建大量线程,同时执行根据下标取值的任务
    for i in range(10):
        #每循环一次创建一个子线程
        sub_thread = threading.Thread(target=get_value, args=(i,))
        sub_thread.start()

#运行结果
1
2
4
下标越界 3

return 前释放锁

import threading

#创建互斥锁
lock = threading.Lock()

#需求:多线程同时根据下标在列表中取值,要保证同一时刻只能有一个线程去取值

def get_value(index):
    #上锁
    lock.acquire()
    list = [1,2,4]
    #判断下标是否越界
    if index >= len(list):
        print('下标越界',index)
        #取值不成功,也需要释放互斥锁,不要影戏后面的线程取值
        lock.release()
        return
    #根据下标取值
    value = list[index]
    print(value)

    #释放锁
    lock.release()

if __name__ == '__main__':
    #创建大量线程,同时执行根据下标取值的任务
    for i in range(10):
        #每循环一次创建一个子线程
        sub_thread = threading.Thread(target=get_value, args=(i,))
        sub_thread.start()

#运行结果
1
2
4
下标越界 3
下标越界 4
下标越界 5
下标越界 6
下标越界 7
下标越界 8
下标越界 9


五、进程与线程的对比
1.关系对比
1.1线程是依附在进程里面的,没有进程就没有线程
1.2一个进程默认提供一条线程,进程可以创建多个线程

2、区别对比
2.1进程之间不共享全局变量
2.2线程之间共享全局变量但是要注意资源竞争的问题,解决办法:互斥锁或者线程同步
2.3创建进程的资源开销要比创建线程的资源开销要大
2.4进程是操作系统资源分配的基本单位,线程是cpu调度的节本单位
2.5进程不能够独立执行,必须依存在进程中
2.6多进程开发比单进程多线程开发稳定性要强

3、优缺点对比
进程优缺点:
优点:可以用多核
缺点:资源开销大
线程优缺点:
优点:资源开销小
缺点:不能使用多核

小结:
1.进程和线程都是完成多任务的一种方式
2.多进程要比多线程消耗资源多,但是多进程开发比单进程多线程开发稳定性要强,某个进程挂掉不会影响到其他进程
3.多进程可以使用cpu的多核运行,多线程可以共享全局变量
4.线程不能单独执行必须依附在进程里面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值