python 线程笔记一 (概念+示例代码)

本文详细介绍了线程的基本概念,包括主线程和子线程的创建、示例代码,以及线程数量的检查。还探讨了线程参数传递、守护线程、并行与并发的区别,自定义线程类的使用,以及如何处理全局变量同步与互斥锁,最后提到了死锁问题及其解决方案。
摘要由CSDN通过智能技术生成

1. 线程的概念

       线程,可简单理解为是程序执行的一条分支,也是程序执行流的最小单元。线程是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属于一个进程的其它线程共享进程所拥有的全部资源。

1.1 主线程

       当一个程序启动时,就有一个进程被操作系统创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程,简而言之:程序启动就会创建一个主线程。

      主线程的重要性体现在两个方面:

(1)主线程是可以产生其它子线程的线

(2)通常它必须最后完成执行,比如执行各种关闭动作

1.2 子线程

     可以看做是程序执行的一条分支,当子线程启动后会和主线程一起同时执行。

1.3 单线程与多线程基本示例代码

单线程:

import time
 
def sayHello():
    print("Hello")
    time.sleep(1)
 
 
if __name__ == '__main__':
    for i in range(5):
        sayHello()

结果:(耗费约5s时间)

多线程:

import time,threading
 
def sayHello():
    print("Hello")
    time.sleep(1)
 
 
if __name__ == '__main__':
    for i in range(5):
        # 创建子线程对象
        thread_obj = threading.Thread(target=sayHello)
 
        # 启动子线程对象
        thread_obj.start()

结果:(耗费约1s时间)

1.4 主线程会等待所有的子线程结束后才结束
import time,threading
 
def sing():
    for i in range(3):
        print("is singing... %d" % i)
        time.sleep(1)
 
def dance():
    for i in range(3):
        print("is dancing... %d" % i)
        time.sleep(1)
 
 
if __name__ == '__main__':
    print("The main thread starts to execute")
    
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
 
    t1.start()
    t2.start()
    
    print("Main thread execution completed")

结果:(可以看到主线程执行完毕后,在等待所有线程执行完毕后才结束运行)

2. 线程的数量

        使用threading.enumerate()可以获取当前所有活跃的线程对象列表,再使用len()查看活跃的线程数量。

import time,threading
 
def sing():
    for i in range(3):
        print("is singing... %d" % i)
        time.sleep(1)
 
def dance():
    for i in range(3):
        print("is dancing... %d" % i)
        time.sleep(1)
 
 
if __name__ == '__main__':
    thread_list = threading.enumerate()
    print("\nCurrent number of threads:%d" % len(thread_list))
 
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
 
    t1.start()
    t2.start()
 
    thread_list = threading.enumerate()
    print("\nCurrent number of threads:%d" % len(thread_list))
 
 

结果:

3.线程的参数

线程传递参数有三种方法:

        1.使用元组传递:

         threading.Thread(target=xxx, args=(参数1,参数2,....))

import time,threading
 
def test(a,b,c):
    print("a=%d,b=%d,c=%d" % (a,b,c))
    time.sleep(1)
 
 
if __name__ == '__main__':
    for i in range(5):
        # 创建子线程对象
        thread_obj = threading.Thread(target=test, args=(10,20,30))
 
        # 启动子线程对象
        thread_obj.start()

结果:

        2.使用字典传递

        threading.Thread(target=xxx, kwargs={"参数名1":"参数值1", "参数名2":"参数值2",...})

import time,threading
 
def test(a,b,c):
    print("a=%d,b=%d,c=%d" % (a,b,c))
    time.sleep(1)
 
 
if __name__ == '__main__':
    for i in range(5):
        # 创建子线程对象
        thread_obj = threading.Thread(target=test, kwargs={"a":10,"c":20,"b":30,})
 
        # 启动子线程对象
        thread_obj.start()

结果:

        3.同时使用元组和字典

        threading.Thread(target=xxx, args=(参数1,参数2,....), kwargs={"参数名1":"参数值1", "参数名2":"参数值2",...})

import time,threading
 
def test(a,b,c):
    print("a=%d,b=%d,c=%d" % (a,b,c))
    time.sleep(1)
 
 
if __name__ == '__main__':
    for i in range(5):
        # 创建子线程对象
        thread_obj = threading.Thread(target=test, args=(10,), kwargs={"c":20,"b":30,})
 
        # 启动子线程对象
        thread_obj.start()

结果:

4.守护线程

        如果在程序中将子线程设置为守护线程,则该子线程会在主线程结束时自动退出,设置方式为thread.setDaemon(True),要在thread.start()之前设置,默认是false的,也就是主线程结束时,子线程依然在执行。

import time,threading
 
def test(a,b,c):
    for i in range(5):
        print("正在输出:%d" % (i))
        time.sleep(1)
 
 
if __name__ == '__main__':
 
    # 创建子线程对象
    thread_obj = threading.Thread(target=test, args=(10,), kwargs={"c":20,"b":30,})
 
    # 设置线程守护:子线程守护主线程
    thread_obj.setDaemon(True)
 
    # 启动子线程对象
    thread_obj.start()
 
    time.sleep(1)
 
    print("主线程即将结束...")
 
    # 退出主线程
    exit()

结果:

5. 并行和并发

5.1 多任务的概念

        其实就是操作系统轮流让各个任务交替执行,如:任务1执行0.01秒,然后任务2执行0.01秒,再让任务3执行0.01秒...这样反复执行下去,表面上看,每个任务都是交替执行的,但是由于CPU执行速度非常快,我们就感觉所有任务都在同时执行一样。

5.2 并发和并行的概念

        并发:并发指的是任务数大于CPU核数,通过操作系统的各种调度算法,实现用多个任务“一起”执行(实际总有一些任务不执行,因为切换任务的速度相当快,看上去一起执行而已)

        并行:指的是任务数小于或等于CPU核数,即任务真的是一起执行的。

        真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以操作系统也会自动把很多任务轮流调度到每个核心上执行。

6. 自定义线程类

        通过threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只需要以下三步:

        1.让自定义类继承 threading.Thread

        2.让自定义类重写run方法

        3.通过实例化自定义类对象.start()方法启动自定义线程

import threading,time
 
 
# 自定义线程类
class MyThread(threading.Thread):
    def __init__(self, num):
        super().__init__()  # 要先调用父类的init方法否则会报错
        self.num = num
 
    # 重写 父类run方法
    def run(self):
        for i in range(self.num):
            print("正在执行子线程的run方法...",i)
            time.sleep(0.5)
 
if __name__ == '__main__':
    mythread = MyThread(5)
    mythread.start()

7. 多线程共享全局变量

        这里创建两个函数,在work1函数中1对全局变量g_num的值进行修改,与此同时在work2函数中获取g_num的值。

import threading,time
 
# 定义一个全局变量
g_num = 0
 
def work1():
    # 申明g_num是一个全局变量
    global g_num
 
    for i in range(10):
        g_num += 1
        time.sleep(0.5)
        print("work1------%d\n" % g_num)
 
def work2():
    for i in range(10):
        time.sleep(0.5)
        print("work2------%d\n" % g_num)
 
if __name__ == '__main__':
    t1 = threading.Thread(target=work1)
    t2 = threading.Thread(target=work2)
 
    t1.start()
    t2.start()
 
    # 在子线程结束后打印g_num
    while len(threading.enumerate()) !=1:
        time.sleep(1)
    print("main------", g_num)

结果:(works1和works2一次输出1-10,只不过各有0.5时间间隔,总的有1s间隔,时间差出现下面这样显示结果)

8. 多线程共享全局变量的问题

import threading,time
 
# 定义一个全局变量
g_num = 0
 
def work1():
    # 申明g_num是一个全局变量
    global g_num
 
    for i in range(1000000):
        g_num += 1
    print("work1------%d\n" % g_num)
 
def work2():
    global g_num
 
    for i in range(1000000):
        g_num += 1
    print("work2------%d\n" % g_num)
 
if __name__ == '__main__':
    t1 = threading.Thread(target=work1)
    t2 = threading.Thread(target=work2)
 
    t1.start()
    t2.start()
 
    # 在子线程结束后打印g_num
    while len(threading.enumerate()) !=1:
        time.sleep(1)
    print("main------", g_num)

结果:

解决方法:

        1.通过join()方法,让t1线程优先执行,t1执行完毕后,t2才执行

        这种方式的缺点是把多线程变成了单线程,影响程序执行效率

import threading,time
 
# 定义一个全局变量
g_num = 0
 
def work1():
    # 申明g_num是一个全局变量
    global g_num
 
    for i in range(1000000):
        g_num += 1
    print("work1------%d\n" % g_num)
 
def work2():
    global g_num
 
    for i in range(1000000):
        g_num += 1
    print("work2------%d\n" % g_num)
 
if __name__ == '__main__':
    t1 = threading.Thread(target=work1)
    t2 = threading.Thread(target=work2)
 
    t1.start()
    t1.join()
    t2.start()
 
    # 在子线程结束后打印g_num
    while len(threading.enumerate()) !=1:
        time.sleep(1)
    print("main------", g_num)

结果:

         2.通过加锁解决问题

9. 同步和异步

        同步:多个任务之间执行的时候要求有先后顺序,必需一个先执行完成之后,另一个才能继续执行,只有一个主线。如:你说完,我再说(同一时间只能做一件事情)

        异步:多个任务之间执行没有先后顺序,可以同时运行,执行的先后顺序不会有什么影响,存在多条运行主线。如:发微信(可以不用等对方回复继续发)

10. 互斥锁

        当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

        互斥锁为资源引入一个状态:锁定/非锁定

        某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其它线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其它线程才能再次锁定该资源,互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

使用互斥锁完成两个线程对同一个全局变量各加100万次的操作:

import threading,time
 
# 定义一个全局变量
g_num = 0
 
def work1():
    # 申明g_num是一个全局变量
    global g_num
 
    lock.acquire()  # 加锁
    for i in range(1000000):
        g_num += 1
    lock.release()  # 解锁
    print("work1------%d\n" % g_num)
 
def work2():
    global g_num
 
    lock.acquire()
    for i in range(1000000):
        g_num += 1
    lock.release()
    print("work2------%d\n" % g_num)
 
if __name__ == '__main__':
    # 创建一把互斥锁
    lock = threading.Lock()
 
    t1 = threading.Thread(target=work1)
    t2 = threading.Thread(target=work2)
 
    t1.start()
    t2.start()
 
    # 在子线程结束后打印g_num
    while len(threading.enumerate()) !=1:
        time.sleep(1)
    print("main------", g_num)

结果:

11. 死锁

        在线程共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。(比如:一条流水线负责生产螺帽,一条流水线负责生产螺母,而且两条流水线都需要负责组装,就会出现螺母等螺帽,螺帽等螺母的情况,又因为各条流水线都资源上锁,对方都拿不到,因此造成死锁)

示例:

import threading
 
 
def getValue(index):
    dataList = [1,3,5,7,9]
    lock.acquire()
 
    if index >= len(dataList):
        print("下标越界",index)
        return
    print(dataList[index])
    lock.release()
 
if __name__ == '__main__':
    # 创建锁
    lock = threading.Lock()
 
    # 创建10个线程
    for i in range(10):
        t = threading.Thread(target=getValue, args=(i,))
        t.start()

结果:(发现当下标越界后程序被阻塞了,原因是return之前没有释放锁)

解决:在return之前释放锁

import threading
 
 
def getValue(index):
    dataList = [1,3,5,7,9]
    lock.acquire()
 
    if index >= len(dataList):
        print("下标越界",index)
        lock.release()
        return
    print(dataList[index])
    lock.release()
 
if __name__ == '__main__':
    # 创建锁
    lock = threading.Lock()
 
    # 创建10个线程
    for i in range(10):
        t = threading.Thread(target=getValue, args=(i,))
        t.start()

结果:

  • 28
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值