协程库——面试问题

0 简单介绍项目

项目描述:
本项目为了应对服务端 IO密集型任务的处理, 在Linux环境下使用C++开发了一个协程库。项目主要实现了协程和协程调度器,定时器等。同时,对常见系统API,如sleep系列,socket IO 相关的API进行hook,实现异步效果。
测试:
使用原生epoll和本项目分别编写简单服务器,在单线程条件下,本项目没有什么性能损失;
在大流量、io密集、多线程条件下,经常需要等待IO导致线程频繁切换,相比于原生epoll,使用协程可以带来大幅的性能提升。
主要工作:
封装:基于RAII思想,封装了pthread_mutex_t、pthread_rwlock_t,实现范围锁;
协程实现:基于ucontext_t实现协程类,使用非对称协程模型,每个协程使用独立栈空间。协程设计了READY/RUNNING/TERM三种状态;协程之间通过 resume/yield进行切换;
定时事件:基于时间堆实现定时器,支持取消、刷新和重置定时器;
IO事件:封装[fd-fd注册的事件-事件的回调函数]为FdContext类,支持添加、取消和删除事件;
协程调度:支持普通协程的调度和IO事件/定时事件的回调。调度器内部实现 调度线程池和任务队列,调度线程循环从任务队列中取出任务然后进行调度,当任务队列为空时,调度线程通过epoll_wait阻塞在idel协程中,idel协程负责在事件发生/定时器超时后,将回调函数添加到任务队列中;
协程池:每个调度线程中分别创建一个协程池,调度时,将任务绑定到协程池中空闲协程上进行调度;
hook模块:hook系统底层socket相关、socket IO相关以及sleep系列的API。若系统阻塞则自动注册IO事件或定时事件,然后让出cpu,当事件发生或定时器超时再继续执行,从而使一些不具备异步功能的API,展现出异步的效果。
测试:使用原生epoll和本项目分别编写简单服务器,使用ApacheBench进行压力测试,单线程条件下本项目没有性能损失,在多线程、大流量、IO密集条件下,本项目有一定的性能提升。

什么是异步效果?

IO模型有同步和异步两种。比如:

  • 使用同步阻塞IO读数据,如果数据未准备好,就会阻塞等待;
  • 使用同步非阻塞IO读数据,如果数据未准备好,不会阻塞,直接返回,用户要么不断轮询,要么使用IO多路复用,监听fd的读事件;
  • 异步IO读数据,不管数据是否准备好,都立即返回。IO操作完成之后,再通知用户。

而本项目中: 

想要读数据,直接调用read(),不管数据是否准备好,都立即返回,cpu继续执行其它任务。这实现类似异步的效果。

实际上,对read()进行hook后,read()会为fd注册读事件,调度器内部通过epoll_wait对IO事件进行监听,事件发生后,对应的回调函数会被添加到调度队列中,等待调度器调度。但这些操作对用户都是透明的。

通过本项目,用户可使用同步的方式编写代码,实现异步的效果

但是,这并不是真正的异步,底层使用的还是同步IO,只不过表现出来的是异步的效果。

为什么不直接使用异步IO?

相比同步Io,异步IO需要管理每个IO操作的状态,学习和使用较为复杂。

基于简历的问题

1 RAII

Resource Acquisition is Initialization,资源获取即初始化。

使用类来管理资源,在构造函数中申请分配资源,在析构函数中释放资源,将资源和对象的生命周期绑定。智能指针是RAII最好的例子。

范围锁:基于RAII,在构造函数中上锁,在析构函数中解锁。

2 锁

2.1 项目中哪里使用锁?

        工作线程从任务队列中取任务,需要互斥锁(比读写锁更安全)

        读写全局变量,使用读写锁

2.2 锁的种类

互斥锁

读写锁

自旋锁:循环等待获取锁

信号量、条件变量

数据库中的锁:

  • 悲观锁:假定访问数据时冲突才是常态,访问数据之前就上锁,防止其它线程的操作,直到当前事务结束。适合写多读少的场景。
  • 乐观锁:假定访问数据时冲突较少,访问数据时不加锁,而是在更新数据之前检查数据是否被其它事务更改过,只有数据未修改,当前操作才会成功。
  • 行锁、表锁、共享锁、排他锁
2.3 为什么不用c11的线程和锁

std::thread也是基于pthread实现的,但是在c11中没有读写锁,这在多线程开发中需要频繁使用,所以选择自己封装。

tips:c++14中提供了读写锁。

c++11中的互斥锁:

  • std::lock_guard:对象创建,自动上锁,对象析构,自动解锁。
  • std::unique_lock:在lock_guard的基础上,用户可以手动上锁和解锁

3 ucontext_t接口

4 对称协程、非对称协程 

5 有栈协程、无栈协程

6 独立栈、共享栈

7 为什么只有三种状态

  • 模型更加简洁,易于管理和实现。
  • 降低维护成本,减少了出错的可能。

8 定时器的实现方式

9 线程池的实现

本项目中,调度器包含调度线程和任务队列,调度器中的线程池是一个线程数组,调度器本身行使了传统线程池的任务。

传统的线程池包含以下几个要素:

  • 工作线程
  • 任务队列
  • 线程同步

c++11线程池示例

#include<queue>
#include<thread>
#include<mutex>
#include<conditon_variable>
#include<functional>
#include<vector>

class ThreadPool {
public:
	ThreadPool(size_t threadCount) : stop(false) {
		for (size_t i = 0; i < threadCount; ++i) {
			//根据参数,调用对应的构造函数
			workers.emplace_back([this] {
				//工作函数的逻辑
				while (true) {
					std::function<void()> task;
					{
						std::unique_lock<std::mutex> lock(queueMutex);
						//释放lock;被唤醒后,也要匿名函数返回true,才能继续执行,否则继续等待
						condition.wait(lock, [] {return stop || !task.empty(); });
						if (stop && task.empty())
							return;
						//移动
						task = std::move(tasks.front());
						tasks.pop();
					}
					task();
				}
			});
		}
	}

	~Thread_pool() {
		{
			std::unique_lock<std::mutex>lock(queueMutex);
			stop = true;
		}
		condition.notify_all();
		for (std::thread& worker : workers) {
			worker.join();
		}
	}

	void enqueue(std::function<void()> f) {
		{
			std::unique_lock lock(queueMutex);
			task.emplace_back(std::move(f));
		}
		condition.notify_one();
	}

private:
	std::vector<std::thread> workers;
	//返回类型void,()中是参数类型,无参数
	std::queue<std::function<void()>> tasks;
	std::mutex queueMutex;
	std::condition_vairable condition;
	bool stop;
};

void printHello(int id) {
	std::cout << "Hello from thread " << id << std::endl;
}

int main() {
	ThreadPool pool(4);
	for (int i = 0; i < 10; ++i) {
		pool.enqueue([i] {printHello(i); });
	}
	return 0;
};

10 hook的作用,怎样实现?

hook将原来的api进一步封装,在执行真正的系统调用之前,执行一些隐藏的操作,从而实现异步的效果,如下:        

        hook socket IO,read/write:如果暂时无法读/写,则为fd注册对应事件,yield,等待事件发生后,再把这个协程添加到调度队列。

        用户不需要使用epoll进行监测,可使用顺序编程的方法,实现异步的效果。IO调用返回后,IO就已经完成,中间过程不需要用户参与。

int fd = accept()
read(fd)
...
write(fd)
...

其它hook: 

  • socket:如果用户没有设置为阻塞模式,则设置fd为非阻塞
  • sleep:注册定时事件,然后yield,超时后,再把这个协程添加到调度队列

11 用了什么设计模式?

单例模式。

创建fd管理类FdCtx,标识fd是否阻塞,是否是socket fd,是否关闭;

创建FdCtx集合类FdManager管理所有FdCtx,FdManager使用单例模式。

为什么要使用单例模式?

        主要是为了提供一个全局访问点,方便访问;保证全局仅有一份实例,节约内存资源;避免多个实例产生冲突。

        FdManager是全局使用的,并不是某个类的成员,似乎可以使用全局变量来实现。但是:

  • 全局变量可能会被拷贝,导致内存中出现多个实例,浪费内存;
  • 遵循设计模式,代码更规范。

注意:单例模式本身无法避免线程同步问题,需要使用互斥锁等

 单例模式的优点:

http://t.csdnimg.cn/3m8aD

12 还熟悉什么设计模式

12.1 观察者模式

定义一种一对多的模型,主要分为主题和观察者两部分,主题发生变化时,通知所有观察者。

应用场景:

  • 事件处理系统,某些事件被触发,相关的处理器会被调用;
  • 发布-订阅模式,微信公众号发布一条消息,会推送给所有的订阅者
12.2 工厂模式

是一种创建型设计模式,提供了一种创建对象的最佳方式。创建对象的过程被封装在工厂类中,这个类负责实例化对象。将对象的创建和使用分离,代码更加灵活。

简单工厂模式

  • 包含一个静态工厂方法,用于创建具体产品对象;
  • 缺点:每增加一个产品类,就需要修改工厂类,违反开闭原则;

工厂方法模式

  • 定义工厂接口,每个用于创建具体产品的工厂类都实现工厂接口;
  • 符合开闭原则;更好的扩展性;
  • 每个产品都对应一个工厂类,增加了系统复杂度;

工作流程

  • 定义产品接口;
  • 创建具体产品类,实现产品接口;
  • 定义工厂接口;
  • 创建具体工厂类,实现工厂接口;

应用场景

  • 创建逻辑复杂时
  • 需要使用不同的类生产不同的产品时

1 IO模型

1.1 同步

1.1.1 阻塞IO

 用户执行read,会阻塞等待数据准备好、数据从内核拷贝到应用进程两个过程,拷贝完成,read才会返回。

1.1.2 非阻塞IO

read在数据未准备好时,立即返回,并设置错误码 EAGAIN/ EWOULDBLOCK,此时应用程序不断轮询内核,直到数据准备好,然后将数据从内核拷贝到用户缓冲区中,read返回。

read最后一次,需要等待数据从内核拷贝到用户缓冲区,这是同步的过程。

1.1.3 IO多路复用

 I/O 多路复用接口最大的优势在于,用户可以在一个线程内同时处理多个 socket 的 IO 请求

同样,read需要等待数据从内核拷贝到用户缓冲区,这是同步的过程。

1.2 异步

异步IO,内核数据准备好、数据从内核拷贝到用户态,都不需要等待。 

用户调用aio_read后,立即返回,内核自动准备数据并拷贝到用户态,不需要等待,这是个异步的过程。拷贝完成后,通知用户。

1.3 总结

同步IO一定会阻塞在【过程2】,而异步IO不会阻塞。 

进程、线程、协程区别(高频)

进程是操作系统进⾏资源分配的基本单位,每个进程都有⾃⼰的独⽴内存空间;

线程是cpu调度的基本单位,线程共享父进程的虚拟地址空间,每个线程有自己独立的栈空间,用于函数调用和存储局部变量;

协程本质上是一个函数,但是它有运行状态,可以像线程一样进行切换。协程是可以理解为用户态线程,协程的运行需要依赖线程。

2.1 切换上下文

进程

cpu上下文——寄存器

虚拟内存相关——页表

资源相关——文件句柄

线程

线程切换不需要切换虚拟地址空间,

只需要切换cpu寄存器上下文和少量的资源管理上下文

协程

部分cpu寄存器,

当前调用栈栈基地址代码的执行位置等,当前的上下文保存到线程的堆区

2.2 多进程/线程/协程

多进程

fork()创建子进程,子进程拷贝父进程地址空间,写时复制,代码段相同,执行任务相同

exec 系列函数可以在子进程中加载新的可执行程序,将子进程的代码替换为新程序的代码。这样,子进程将执行与父进程不同的任务。

多线程

父进程创建多个线程,每个线程有自己的入口函数,执行不同的任务。多个线程共享父进程的资源。

协程

每个协程由自己的入口函数,执行不同的任务。

协程通常是在单线程中运行的,协程可以在线程中实现切换,开销比线程和进程切换小,可以实现高并发。

协程经常与多线程一起使用。

3 协程/协程库的优点(高频)

协程优点?协程库的优点?

一方面:

协程较为轻量级,创建和销毁开销小。在有限的资源下,可以创建更多的协程,实现更高的并发

在服务端场景下,发生阻塞导致线程切换的场景有很多,比如互斥锁冲突,等待其它线程的消息,sleep等,协程切换的开销小,速度比线程更快。可以提升吞吐量

另一方面(协程库):

协程库对常见系统api进行hook,比如read,write,用户使用同步编程的方式,就可以实现异步的效果

缺点

⽆法利⽤多核资源:线程才是系统调度的基本单位,单线程下的多协程本质上还是串⾏执⾏的,只能⽤到单核计算资源,所以协程往往要与多线程、多进程⼀起使⽤。 

难以调试:由于协程的切换和异步执行,调试协程代码可能更加困难。当协程之间存在复杂的依赖关系和交互时,追踪问题的根因可能变得复杂。

4 协程适用于I/O密集型任务的原因

非阻塞IO条件下,等待IO的过程中,会切换到其它任务继续执行:

        相比线程,协程切换快速,开销小;

        协程轻量级,占用资源少,可以创建大量协程,处理并发,不会像线程一样收到资源的限制。

5 协程实现的是真正的异步吗?

底层使用的是同步非阻塞IO,还是同步的,但是可以实现异步的效果,调用返回后,数据就已经读取完成,期间,不需要用户干预。

衡量⼀个协程库性能的标准

响应时间

吞吐量

并发能力:同时处理的协程数量

上下文切换开销

资源利用率:cpu,内存,网络等。可以在资源利用的方面进行优化,从而提升性能。

7 Go协程

Go从语言层面支持协程,Goroutine就是Go中最基本的执行单元。每一个Go程序至少有一个Goroutine,从main函数开始,Go程序会为main函数创建一个默认的Goroutine。

8 C++协程

是c++20引入一种语言新特性,通过co_await和co_yield实现

9 为什么要有空闲协程

在任务队列为空时,阻塞在idel协程中的epoll_wait中。idel协程负责使用epoll监听事件,实际发生后,将对应回调函数添加到调度队列中。

调度协程只负责任务调度,idel协程负责添加任务,这样,降低了不同功能之间的耦合,便于后序扩展和维护。

10 每建⽴⼀个⽤户连接就要创建⼀个协程,不会影响性能吗?

 会的,高并发时,会有大量的协程创建和销毁,会占用较多系统资源。

可使用协程池的方法解决。提前创建一定数量的协程,有新的任务时,直接复用已有的协程。

11 测试+优化

11.1 测试

测试方法:使用原生epoll和本项目的协程库,分别在单线程和多线程条件下,编写服务端程序。服务端接收到请求后,回复一个简单的页面。

测试工具:Apache Bench(ab)

结论:        

  • 单线程情况下,相比于直接使用epoll,性能并无太大差异。
  • 多线程情况下,在IO密集、线程或协程切换情况较多时,使用协程有明显的性能优势。

11.2 优化——协程池

每个调度线程中,创建一个协程池。

开始调度之前,先创建一定数量的协程;

调度时,选择空闲的协程绑定任务进行调度。如果没有空闲的协程,则创建一个新的协程,可以选择是否添加进入协程池。

协程池的存在,避免了部分协程的创建和析构,在一定程度上提升了系统的性能。

协程池中协程的数量选择:

如果不需要等待IO,或任务执行的过程中,不需要yield:因为在线程中,协程是串行执行的,执行完一个,再执行另一个。同一时间,只有一个协程在执行,且没有处于挂起状态的协程(本项目中是ready状态),那么,协程池中只需要一个协程就足够,提升协程的数量不能提升性能;

如果,需要等待IO,或任务执行的过程中,需要yield:同一时刻,有大量被挂起的协程,还有一个正在执行的协程,那么,不考虑内存影响,协程池的协程数量越多,性能提升越大。为了简单,本项目选择创建新的协程,并且新的协程不添加入协程池。

12 困难(高频)

12.1 协程的调度

困难1

使用非对称协程模型,子协程只能和调度协程切换,调度协程再选择新的任务协程进行调度,

子协程不能直接切换到另外的子协程。

子协程不能切换到另外的子协程,那怎样实现在子协程中调度另一个任务呢?

解决办法

需要保证,在子协程中可以访问到其所属的协程调度器;然后在子协程中创建一个新的协程,并将新的协程添加到调度器的任务队列中,等待调度。这变相的实现了在子协程中调度别的任务。并不是直接切换到另一个子协程。

访问调度器具体实现:

协程调度器中包含调度线程池,调度线程中创建一个线程局部变量,指向所属的协程调度器。那么,所有运行在调度线程上的协程也就可以放问协程调度器。

困难2

原始版本:调度协程需要负责添加调度任务和具体的任务调度,职责相对复杂,不利于理解和维护;

解决办法

将添加调度任务、具体的任务调度两个模块分离,使调度协程只负责任务的调度,职责更加清晰。创建一个单独的协程idel负责添加调度任务。当任务队列为空,切换到idel协程,在idel协程中,通过epoll_wait阻塞,等待新任务到来。新任务到来后,idel协程负责添加到任务队列,然后会切换到调度协程,开始调度。

降低了不同功能之间的耦合,便于后序扩展和维护。

12.2 hook

困难

怎样使用户通过同步编程的方式,实现异步效果

解决办法

对IO系统调用进行封装:

比如read()如果数据未准备好则为fd注册对应IO事件,然后主动退出当前协程,切换到调度协程。调度协程选择任务队列中的其它任务进行调度

此处,涉及到协程的切换

epoll监测到IO事件发生后,调度器(idel协程将对应的回调函数添加到任务队列中等待调度。回调函数是read()所在的协程本身,当协程被重新调度时,会恢复其上下文,从上次切出的点开始执行。此时,数据已经准备完毕,可以直接读取。

此处,涉及到协程任务的添加,协程的切换,idel协程的作用

对用户来说,只需要调用一次read(),立即返回,协程库完成其它的事情,不需要像IO多路复用,进行多个判断。使用起来和异步IO类似。

这样,用户可以使用同步的编程方式(是同步变成的方式吗????不确定!!!),实现异步的效果

底层其实是对IO多路复用的流程进行了封装。

一些理解

IO多路复用流程

使用epoll + 线程池,为fd注册事件,当事件发生,向线程池的任务队列添加任务,线程池中的工作线程获取任务并执行。

比如,检测到EPOLLIN事件,服务端主线程read读取数据,然后将数据交给工作线程,工作线程解析数据,生成响应,并为fd注册写事件;当EPOLLOUT发生,服务端主线程将响应数据发送给客户端。

协程的使用

直接对fd调用read(),不考虑内部细节,read()成功后,才能向下继续执行。如果read()阻塞,会切换到任务队列中的其它协程执行,当返回当前位置,说明read已经执行完成。

read成功后,将对请求数据的处理和write响应客户端的过程封装为一个函数,然后添加到调度队列中,等待调度。

阻塞的场景

考虑这样的场景,比如同步机制互斥锁、读写锁冲突较多,等待其它线程的消息,sleep等:

        线程阻塞,必须得切换到其它线程;

        协程阻塞,切换到其它协程。显然,协程效率更高。

我们可以使用sleep来模拟非常极端的线程阻塞场景。

13 收获 

  • 深入了解了协程和协程调度,熟悉独立栈/共享栈,对称/非对称协程的概念
  • 对进行、线程加深了理解,熟悉了其区别
  • 熟悉了Linux网络编程,熟悉IO多路复用,epoll

14 其它协程库 

c++20协程

go协程

Boost.Coroutine2

libco:腾讯

15 为什么做这个项目(高频)

首先,是出于好奇,想要了解协程和线程进程的区别,然后就去找资料学习;

其次,协程确实有很大的作用,比如,在服务端高并发场景中,协程的切换的开销比线程小,创建和销毁的开销小,协程比线程更有优势;

然后,我参考一个开源的c++服务器框架,选择其中的协程模块进行学习,实现了这个协程库。

16 有什么可以提升的地方?(高频)

16.1 任务调度顺序

        当前项目中,任务添加到调度队列中,是按照先来先服务的顺序调度的,没有考虑任务的优先级。可以优化的点是使用更合理的调度方式,比如设置优先级,时间片轮转等。

16.2 架构

非对称协程优点,缺点;对称协程优点,缺点

非对称缺点:

        子协程只能和线程主协程切换,意味着子协程无法创建并运行新的子协程。

解决办法:

        定义一个线程局部变量,指向协程调度器,协程运行在线程之上,可以访问到调度器,从而添加任务到调度队列中。  子协程中添加新的调度任务的方法:sylar::Scheduler::GetThis()->schedule(test_fiber5, sylar::GetThreadId()); 

仍存在的不足之处:

        在实现协程调度时,完成一次子协程调度需要额外多切换一次上下文(当前协程-主协程-另一个协程 )。

17 怎样将协程库接入别的项目

协程库使用的时候:

创建协程调度器,调度器内部封装了线程池和任务队列;

对于一个任务,在不使用协程库的情况下,需要交给一个线程去执行;

使用协程库,只需要将任务添加到调度器的任务队列中就好,就像使用线程池一样。

所以总体上改变不大。

18 调度器怎样实现的?

调度器内部包含调度线程池任务队列,和线程池相似。内部封装了IO多路复用的流程。用户添加任务到任务队列,任务可以是协程或一个函数,调度线程从任务队列中取任务然后执行。任务有几种,分别是普通的计算任务,IO任务,定时任务。

如果是IO任务,实现了hook。如果发生了阻塞,那么注册事件、切出、事件发生、回调,实现了异步效果。

定时任务,添加到定时器容器中,使用set实现,当定时器超时,回调函数被添加到任务队列。

旧版本简历

项目描述:
本项目在Linux环境下使用C++开发了一个协程库,适合socket IO密集型任务的处理。协程基于ucontext_t实现,使用非对称协程模
型。结合epoll和定时器实现了协程调度器,支持cpu任务的调度以及socket IO事件、定时事件的回调。同时,对常见系统API进行
hook,实现异步效果。
主要工作:
锁的封装:基于RAII思想,封装了pthread_mutex_t、pthread_rwlock_t;
协程实现:基于ucontext_t实现了非对称协程,每个协程有独立栈空间。协程有READY/RUNNING/TERM三种状态,协程使用
resume/yield来获取/让出cpu;
N-M协程调度器:调度器内部实现一个调度线程池和任务队列(存放待调度的协程),调度线程循环从任务队列中取出任务然后进
行调度。当任务队列为空时,调度线程进入idel状态,等待新的调度任务。调度器支持使用main函数所在线程进行调度;
IO协程调度器:继承自N-M协程调度器,封装了epoll,支持注册socket fd读/写事件的回调。当任务队列为空时,调度线程阻塞
在epoll_wait上,当注册的IO事件发生或添加了新的调度任务时再返回;
定时器:基于时间堆实现,通过定时器可以给服务器注册定时事件。定时器超时,则将定时器的回调函数添加到任务队列中进行调
度;
Hook模块:基于IO协程调度器和定时器实现,hook系统底层socket相关、socket IO相关、以及sleep系列的API。系统阻塞则注
册IO事件或定时事件,然后让出cpu,当事件发生或定时器超时再返回,从而使一些不具备异步功能的API,展现出异步的性能。

### Python 工程师面试常见问题及解答 #### 多线程编程是否适合Python? 多线程在Python中的应用存在争议,主要是由于全局解释器锁(GIL)的存在使得CPU密集型任务无法真正并行执行[^1]。然而,在I/O密集型场景下,如网络请求或文件操作,多线程仍然可以提高程序效率。 对于希望实现并发处理的情况,除了多线程之外还有其他几种方法: - **多进程**:利用`multiprocessing`来绕过GIL限制,适用于计算密集型任务。 - **异步IO**:借助于`asyncio`框架以及`aiohttp`等第三方来进行高效的网络通信或其他形式的输入输出操作。 - **协程**:通过定义带有`yield`关键字的生成器函数或者使用更现代的方式——基于`async/await`语法糖构建轻量级的任务单元。 ```python import asyncio async def fetch_data(): await asyncio.sleep(1) # Simulate network delay return {"data": "value"} async def main(): task = asyncio.create_task(fetch_data()) data = await task print(data) # Run the event loop asyncio.run(main()) ``` #### 如何判断字符串是否为回文? 为了验证给定单词是否构成回文结构,可以通过比较原串与其反转后的版本是否相等完成此功能[^2]。下面是一个简单的例子展示如何编写这样的逻辑: ```python def is_palindrome(word): cleaned_word = ''.join(filter(str.isalnum, word)).lower() reversed_word = cleaned_word[::-1] return cleaned_word == reversed_word if __name__ == "__main__": test_string = "A man, a plan, a canal: Panama" result = is_palindrome(test_string) print(f"'{test_string}' 是不是回文? {result}") ``` 这段代码不仅考虑到了大小写敏感性还过滤掉了非字母数字字符以确保准确性。 #### `pass`语句的作用是什么? 当开发者想要声明某个地方应该有某些行为但是暂时不想具体实现时就可以用到`pass`语句;另外一种情况是在测试期间作为占位符以便后续补充实际业务逻辑[^3]。需要注意的是虽然`pass`看起来什么都没做但它确实是一条有效的指令并且可以在任何合法的位置出现而不会引发错误。 ```python class PlaceholderClass: pass # This class does nothing but can be extended later. def placeholder_function(): pass # Function body will be implemented in future. ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值