【Python】线程和多线程的使用

2024-01-08 00:51:43

原文作者:我辈李想
版权声明:文章原创,转载时请务必加上原文超链接、作者信息和本声明。



前言


一、基本概念

线程是计算机中的基本执行单元。一个进程中可以拥有多个线程,这些线程可以并发地执行不同的任务。线程共享进程的资源,包括内存空间、文件和其他系统资源。通过使用多线程,程序可以在同一时间执行多个任务,提高系统的并发性和响应性。

线程可以分为用户线程和内核线程。用户线程是由应用程序开发者创建和管理的,它们只在用户空间内运行,不需要内核的支持。而内核线程是由操作系统内核创建和管理的,它们在内核空间内运行,并通过系统调用来与操作系统进行交互。

线程的创建和销毁是轻量级的操作,相比于进程的创建和销毁,更加高效。线程可以共享进程的地址空间,因此可以直接访问进程的全局变量和数据结构。但是,由于多个线程共享同一片内存空间,需要进行同步和互斥来保证数据的一致性和可靠性。

线程可以实现并行和并发的效果,使得程序可以同时执行多个任务。并行是指多个任务在同一时刻同时执行,而并发是指多个任务交替执行,通过时间片轮转使得多个任务共享CPU的时间。通过合理地使用线程,可以提高程序的性能和效率。

二、python线程

Python中的线程是基于线程切换的,并不是真正的并行执行。这是因为Python中的全局解释器锁(GIL)限制了解释器中同一时间只能有一个线程执行Python字节码。如果需要实现真正的并行执行,可以使用多进程(multiprocessing模块)或者其他第三方库(如concurrent.futures)来实现。
Python 的多线程更适合于 I/O 密集型的任务,而不是 CPU 密集型的任务。单线程下有IO操作时,会进行IO等待,这样会浪费等待的这段时间,而开启多线程能在线程A等待时,自动切换到线程B,可以减少不必要的时间浪费,从而能提升程序运行效率。但是也不是最好的选择,对于处理IO密集型任务,在Python还有更好的选择协程。

1.函数调用

import threading

def task():
    # 线程的任务
    print('111')

threading.Thread(target=task, name='dance').start()

2.类的调用

import threading

class MyThread(threading.Thread):
    def __init__(self):
        super().__init__()
        # self.num = num # 自定义参数

    def run(self):
        # 线程的任务
        print('111')
        return 'ok'

if __name__ == '__main__':
    my = MyThread()
    # 如果有参数
    # my = MyThread(num=1)
    my.start()

三、共享全局变量

"""多线程共享全局变量"""
import threading
import time


g_num = 100  # 这个要使用global
list = [1,2]  # 这个在函数中执行的时候要是不改变他的指向就不需要使用global


def test1():
    """改变全局变量"""
    global g_num
    g_num += 1
    list.append(1)
    print('-----test1----g_num:%d----' % g_num)
    print(f'list初始:[1,2]检查是否可以在不使用global的情况下使用全局变量:{list}')


def test2():
    """打印全局变量g_num,如果共享则打印的全局变量为101"""
    global g_num
    print('----test2----g_num:%d----' % g_num)
    print(f'----test2----list:{list}----')


def main():
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)

    t1.start()
    time.sleep(1)  # 确保test1先执行
    t2.start()
    time.sleep(1)

    print('----in main g_num = %d----' % g_num)


if __name__ == '__main__':
    main()

互斥锁建议参考:python多线程讲解_V-Sugar的博客-CSDN博客

四、守护线程

在 Python 中,线程对象的 daemon 属性用于指定线程的守护状态。一个守护线程是指在主线程退出时会被强制终止的线程。如果一个线程被设置为守护线程,那么当最后一个非守护线程退出时,守护线程也会立即退出,不管它是否完成了自己的任务。

可以通过线程对象的 setDaemon() 方法将线程设置为守护线程。该方法接受一个布尔值参数,True 表示将线程设置为守护线程,False 表示将线程设置为非守护线程。

下面是一个示例代码,演示了如何创建和设置守护线程:

import threading
import time

def print_numbers():
    for i in range(1, 6):
        print(i)
        time.sleep(1)

# 创建线程对象
thread = threading.Thread(target=print_numbers)

# 设置线程为守护线程
thread.setDaemon(True)

# 启动线程
thread.start()

# 主线程休眠3秒
time.sleep(3)

# 主线程结束,守护线程也会立即退出
print("Main thread exiting...")

在上面的例子中,我们创建了一个线程对象,并将它设置为守护线程。当主线程休眠了3秒之后,主线程结束并退出,守护线程也会立即退出,即使它没执行完毕。

需要注意的是,守护线程不应该执行一些重要的任务,因为在主线程退出时守护线程会被强制终止,可能导致任务未完成或数据不一致。守护线程通常用于执行一些后台或周期性的任务,如日志记录、定时器等。

五、线程锁

在 Python 中,可以使用线程锁(Thread Lock)来确保在多线程环境下的数据同步和线程安全。线程锁是一种同步机制,可以防止多个线程同时访问共享资源,从而避免出现数据竞争和不一致的情况。

Python 提供了 threading 模块中的 Lock 类来实现线程锁。可以使用 Lock 类的 acquire() 方法获取锁,在执行需要同步的代码块之前,需要先获取锁。在代码块执行完毕后,使用 release() 方法释放锁,让其他线程可以获取锁并继续执行。

下面是一个示例代码,演示了如何使用线程锁:

import threading

# 创建一个线程锁对象
lock = threading.Lock()

# 共享资源
counter = 0

def increment():
    global counter

    # 获取锁
    lock.acquire()
    
    # 访问共享资源
    counter += 1
    
    # 释放锁
    lock.release()

# 创建多个线程
threads = []
for _ in range(10):
    thread = threading.Thread(target=increment)
    threads.append(thread)
    
# 启动线程
for thread in threads:
    thread.start()

# 等待所有线程执行完毕
for thread in threads:
    thread.join()

# 打印最终结果
print("Final counter value:", counter)

在上面的例子中,我们创建了一个线程锁对象,并定义了一个共享资源 counter。在 increment() 函数中,我们首先使用 acquire() 方法获取锁,然后对 counter 进行递增操作,最后使用 release() 方法释放锁。

通过使用线程锁,我们确保了在每个线程访问 counter 时,只有一个线程可以获取锁并执行递增操作,从而保证了最终结果的正确性。

需要注意的是,在使用线程锁时需要小心避免出现死锁(Deadlock)情况。当多个线程相互等待对方释放锁时,可能会导致死锁的发生,使得程序无法继续执行。因此,在设计多线程程序时,需要合理地使用锁,避免出现潜在的死锁问题。

如果使用with的话,可以不用acquire和release。

def increment():
    global counter
    with lock:
	    # 访问共享资源
	    counter += 1

六、杀死进程

import os
import threading


def kill(pid):
    # 本函数用于中止传入pid所对应的进程
    if os.name == 'nt':
        # Windows系统
        cmd = 'taskkill /pid ' + str(pid) + ' /f'
        try:
            os.system(cmd)
            print(pid, 'killed')
        except Exception as e:
            print(e)
    elif os.name == 'posix':
        # Linux系统
        cmd = 'kill ' + str(pid)
        try:
            os.system(cmd)
            print(pid, 'killed')
        except Exception as e:
            print(e)
    else:
        print('Undefined os.name')


class MyThread(threading.Thread):

    def __init__(self):
        super().__init__()
        # self.num = num # 自定义参数

    def run(self):
        # 线程的任务
        print('111')
        return 'ok'


if __name__ == '__main__':

    # 1.读取上次保存的pid
    f1 = open(file='feishu_pid.txt', mode='r')
    pid = f1.read()
    f1.close()
    # 2.如果存在杀死上一次的进程
    print('上一次进程', pid)
    if pid:
        # 调用kill函数,终止进程
        kill(pid=pid)

    # 3.获取当前进程的pid
    pid = os.getpid()
    print('当前进程的pid: ', pid)

    # 4.将pid写入本地文件,供下次调用
    f2 = open(file='feishu_pid.txt', mode='w')
    f2.write(pid.__str__())
    f2.close()
    # 5.开启新的线程
    my = MyThread()
    # 如果有参数
    # my = MyThread(num=1)
    my.start()

文章来源:https://blog.csdn.net/qq_15028721/article/details/135341860
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。