BOOST源码笔记(5) - 最小的代价实现同步-无锁编程,看boost中的interlocked_bit_test_and_set实现

boost并发编程boost::atomic boost并发编程 三个用于并发编程的组件: atomic,thread,asio(用于同步和异步io操作) 1. atomic,封装了不同计算机硬件的底层操作原语,提供了跨平台的原子操作功能,解决并发竞争读写变量的困扰。 包含头文件, atomic可以把对类型T的操作原子化,T的要求: 1.标量类型,(算数,枚举,指针) 2.只有平凡拷贝/转移构造、赋值和析构函数的类,并且 阅读详情

如题,如何用最小的代价来实现同步呢,即无锁编程(主要是使用原子操作替代锁来实现对共享资源的访问保护),

我们来看看下面的代码。

首先,我们需要是在这个函数内部对地址x中的值进行位测试并将其对应的位置位。  这里的关键是我们必须先读出*x的值,作位或运算之后,将得到的值对*x赋值。而在多线程环境中,在读出*x后也许有另外的线程对*x进行写操作,这样在给*x赋值的时候就会把对旧的值位或的结果赋给*x,造成错误。

 

这里boost是采用一个循环,将读出的值先保存,这样,读出后的值就为一个不变量,然后计算位或,当然也为不变量。接着用win32中的atom函数interlockedCompareExchange()来对比*x是否在被读出值后改变,如果没改变,则赋值;反之,则更新被保存的变量,再重复判断,直到第前面的情况发生。

 

也许有人会说,如果我这样写呢,                   

long const current=BOOST_INTERLOCKED_COMPARE_EXCHANGE(x,*x|value,*x);

是不是就可以避免读和写不一致。错!你这样还是读了共享变量,而且是2次!2次读出的值都可能不一样,使得可能被位或地值都不一致。而且是在进入atom函数后才是同步代码,而读共享变量值得时候是在函数外部,还需要压栈,完全可能不同步。

 

inline bool interlocked_bit_test_and_set(long* x,long bit)
            {
                long const value=1<<bit;
                long old=*x;
                do
                {
                    long const current=BOOST_INTERLOCKED_COMPARE_EXCHANGE(x,old|value,old);
                    if(current==old)
                    {
                        break;
                    }
                    old=current;
                }
                while(true);
                return (old&value)!=0;
            }

 

以上的函数所用的方法保证了对一个共享值的读-针对读出的值作操作-将操作后的值写回,这3步能够保持同步。我可以类举出win32中interlockedExchangeAdd() <即i++>的实现方法:

 

inline void interlockedExchangeAdd(long* x)
            {
                long old=*x;
                do
                {
                    long const current=BOOST_INTERLOCKED_COMPARE_EXCHANGE(x,old + 1,old); //

比较并交换操作的作用是在一个原子操作内,先将目标值和老的值进行比较,如果相等就将新的值赋给目标变量。目的是为了防止将新的值赋给目标变量前,在读写操作之间目标变量遭到其他线程的修改。


                    if(current==old)
                    {
                        break;
                    }
                    old=current;
                }
                while(true);
            }

 

 

以上就是lock-free编程中的经典的CAS(compare and swap)的实现,除此外Lock-Free编程还有三大优点:

  • 线程中止免疫:杀掉系统中的任何线程都不会导致其它线程被延迟。
  • 优先级倒置免疫:所谓“优先级倒置”就是指一个低优先级线程持有了一个高优先级线程所需要的互斥体。这种微妙的冲突必须由OS内核来解决。而等待无关和锁无关算法则对此免疫。
  • 死锁免疫:因为没有使用锁,所以也就不存在死锁的可能。但是乐观的并发,可能会导致活锁。

虽然Lock-Free编程非常困难,但是它通常可以带来比基于锁编程更高的吞吐量。所以Lock-Free编程是大有前途的技术。它在线程中止、优先级倒置以及信号安全等方面都有着良好的表现。

 

但是CAS会出现ABA问题,这就不在我们的讨论范围内了。转个ABA的解决:

 

即定义额外的标识之类得方法来区别第一次A和第二次的A(不重用A),使得CAS能够察觉变化。

 

 

YOLO26涨点改进 | 独家主干改进篇 | CVPR 2024 | YOLO26利用InceptionNeXt主干,将大核深度卷积分解为四个并行分支,在提升性能的同时显著降低计算效率 本文聚焦YOLO26的核心涨点改进——基于CVPR 2024收录的InceptionNeXt主干网络,深度拆解了其“大核深度卷积四分支拆解”的核心逻辑,通过实验验证了改进的有效性,同时提供了可快速复现的工程代码与进一步涨点的思路。 阅读详情

相关推荐

Ruoyi-Cloud集成Seata2.0实现分布式事务

在网络上,关于Spring Cloud和Ruoyi-Cloud整合Seata的教程有很多,针对不同版本的 Seata 也有不少相关教程。事实上,无论是 Spring Cloud 还是 Ruoyi-Cloud,其整合 Seata 的原理基本相同。这篇博文将详细介绍如何在本地开发环境下,在中实现Seata 2.0的整合,希望能为大家在分布式项目中实现 Seata 的整合提供实用的帮助,提供一个清晰的操作思路。

SR003的博客 2245

明明白白自旋锁

标 题: 明明白白自旋锁 作 者: achillis 时 间: 2008-10-12,13:39:46 链 接:http://bbs.pediy.com/showthread.php?t=74502 自某日看了iceboy和MJ0011关于多处理器同步的一些讨论,才发现原来我对自旋锁的理解也有一定错误,还好现在明白了~~为了加深理解,就深入分析了一下自旋锁的实现,一篇小小的自旋锁分析文

kingswb的博客 1192

紫光同创PGL50H开发板实战:盘古50KN网口板的高速通信与数据处理

本文详细介绍了紫光同创PGL50H开发板(盘古50KN网口板)在高速通信与数据处理领域的实战应用。文章从开箱、核心芯片解析、开发环境搭建入手,重点讲解了如何配置与测试其8路千兆以太网接口和SFP光纤接口,并深入探讨了FPGA与DDR3协同进行多路数据汇聚处理的系统架构与调试方法,为网络通信与高速数据处理系统开发提供了宝贵的实践经验。

weixin_28256233的博客 452

boost学习与应用笔记之一:Boost.Atomic

Presenting Boost.Atomic Boost.Atomic is a library that provides atomic data types and operations on these data types,  as well as memory ordering constraints required for coordinating multiple thr

北雨南萍 3211

AI编码的“黑箱危机”终结者:OpenSpec如何让你的代码从“猜谜”变“契约”?

OpenSpec的真正价值,在于它将AI编码从"经验驱动"推向"契约驱动"——当人类与AI对"目标是什么"达成显式共识,编码就从"猜谜游戏"变成了"工程实现"。这种转变不仅提升效率,更重塑了开发者与AI的关系:AI从"替代者"回归"协作者",而人类则聚焦于"定义正确的事"而非"正确地做事"。对于开发者,OpenSpec提供了驾驭AI的缰绳;对于团队,它构建了协作的基石;对于行业,它预示着AI编码工业化的开端。

haolove527的专栏 1348

ABAP ALV编辑后触发事件

1.FUNCTION ALV在it_events的参数中,写入对应的事件。2.OO ALV和FUNCTION ALV都可以,注册当前ALV的事件,对应的事件需要创建对象去实现。下面代码中,实现的是第一种,在it_events中写入事件。下面的代码是通过创建对象去实现ALV事件。

a86632678的博客 3852

bittest & bittestandset

// bittest.cpp// processor: x86, IPF, x64#include <iostream>#include <stdio.h>#include <intrin.h>//for _bittest() & _bittestandset()typedefunsignedcharbyte;//取32位整形数组的第i(i从0开...

weixin_30563917的博客 295

银河麒麟硬盘健康检测:smartctl/badblocks实战与坏盘预判

xfs_repair检查。S.M.A.R.T检测。

信创运维工程师 · 银河麒麟 & 容器实战 | 金融生产环境迁移·等保加固 | 142篇原创·17万+阅读 859

test_bit、set_bit和clear_bit

//将addr的第nr(nr为0-31)位置值置为1; //nr大于31时,把高27的值做为当前地址的偏移,低5位的值为要置为1的位数 extern __inline__ int set_bit(int nr,int * addr) { int mask, retval; addr += nr >> 5; //nr大于31时,把高27的值做为当前地址的偏移, mask = 1 << (nr &amp...

Android 系统开发 1456

rk3399开发环境的介绍

由于Bluez的介绍文档有限,以及对Linux 系统/驱动概念、D-Bus 通信和蓝牙协议都有要求,加上网络上其实没有一个完整的介绍Bluez系列的文档,所以不管是蓝牙初学者还是蓝牙从业人员,都有不小的难度,学习曲线也相对较陡,所以我有了这个想法,专门对Bluez做一个系统性的介绍,尽可能的涵盖所有内容。

朝气蓬勃 710

18、公开可用的PPG数据库介绍

本文介绍了多个公开可用的PPG(光电容积描记)数据库,包括PPG-BP数据库、MIMIC II波形数据库、运动期间手腕PPG数据库以及指尖PPG与呼吸数据库等。这些数据库提供了丰富的生理信号数据,适用于动脉血压检测、呼吸频率估计、运动干扰去除以及基于PPG的生物特征识别等研究。每个数据库的数据来源、受试者信息、信号特点和下载方式均有详细介绍,并展示了如何利用这些数据库进行信号分析和算法评估。

salt的博客 388

不允许重载函数“_interlockedbittestandreset”的第二个 C 链

编译时遇到的错误及处理方法 不允许重载函数“_interlockedbittestandreset”的第二个 C 链  将vs include file 删除 __MACHINEI(unsigned char _interlockedbittestandset(long volatile *a, long b)) __MACHINEI(unsigned char _interlockedb

TonyAnny专栏 4235

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

关于bit操作(bit_set test_bit)

    今天发现一个奇怪的现象,用linux内核的bitops.h里面的bit_set和test_bit居然没有不用汇编的代码快。记录下来,备忘。    下面是可执行程序,THUIRDB暂时没有bitops里面的实现,改用我自己写的实现了。当然bitops里的实现,nr可以很大,而我的代码nr只能是0-63之间的一个数,对THUIRDB的应用来说,足够了。#include "std

pennyliang的专栏 1万+

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

test_and_set_bit

Man page of test_and_set_bit(int nr, volatile void *addr)这样描述它的功能: "Set a bit and return its old value"  nr -- Bit to set  addr -- Address to count from 注意:nr是需要设置的“哪一位”,而不是将ad

helloworlddm的博客 3077

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

unsigned char _interlockedbittestandset(long volatile *a, long b)问题

因为某种原因,使用了 MSVC的编译器, 但是没有使用 visual studio IDE, 这时候, 如果还使用 旧的SDK package, 这时候在同时 Include intrin.h 和  WinNT.h 的时候, 就会出现这个错误: __MACHINEI(unsigned char _interlockedbittestandset(long volatile *a, long

一亩半分地 2169

_InterlockedAnd _InterlockedOr等原子级安全运算

http://msdn.microsoft.com/en-us/library/windows/desktop/ff471407(v=vs.85).aspx 内部与-安全操作 _InterlockedAnd  _InterlockedOr 在 VS2010 中,在x86  x64编译模式下 ,InterlockedAnd 与_InterlockedAnd 的使用问题

浴血重生-学习空间 2918

_bittest & _bittestandset

C 中的unsigned 类型默认为unsigned int类型。unsigned char为8bit都使用,没有符号位。char < unsigned char < int < unsigned int_bittest, _bittest64 在vs2005以后支持,返回值为unsigned char,如果要得到0或1的值需要进行强制类型转换。检查地址a中,第b位的值是0还是...

weixin_30376509的博客 435

位原子操作

一、理论:1.并发 并发的概念是:多个执行单元同时、并行被执行。 Linux系统是多任务的,很多任务会同时执行。 假如有三个执行单元ABC,共享了内存资源。 执行单元A对Buffer写1000个“a”; 执行单元B对Buffer写1000个“b”; 执行单元C从buffer中读取数据。 如果按照A写→C读→B写→C读,当然没有问题。 但是如果A写→B写→C读,执行单元C就出问题了。 当然比这个复杂...

danwuxie的博客 1040

c++11 std::atomic

reference: c++11 std::atomic https://www.jianshu.com/p/b7a2b36f43d9 使用原子的变量,++ 和 -- 的次数相同,最终为0; 没有使用原则的变量,++ 和 -- 的次数不相同,最终不为0; c++新特性之std::atomic https://blog.youkuaiyun.com/delongwang520/article/details/79604329 ...

Oliver King 的小窝 621

boost的多线程实现机理

对boost的多线程感觉挺好奇的,所以就追踪了以下。 设置在 下面,为了调用windows api,boost是这么做的 # include namespace boost { namespace detail { namespace w

关注3d图形技术,移动互联网 1709

并发学习-【转】深入理解并发之CompareAndSet(CAS)

程老师原文地址:http://flychao88.iteye.com/blog/2269438 原文如下: 一、CAS简介 CAS:Compare and Swap, 翻译成比较并交换。 java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁,使用这些类在多核CPU的机器上会有比较好的性能. CAS有3个操作数,内存值V,旧的预期...

bohu83的博客 1万+

7.3 实现顺序死锁代码

简单顺序死锁的条件 1、两个线程1,2 2、两个资源a,b 3、两个线程先获取不同的资源(使用Thread.sleep) demo public class TestDeadLock { static class CustomerA extends Thread{ Object a; Object b; public CustomerA(Object a, Object b) { this.a = a; ...

一点光辉的博客 173

Java里的CompareAndSet(CAS)

Atomic 从JDK5开始, java.util.concurrent包里提供了很多面向并发编程的类. 使用这些类在多核CPU的机器上会有比较好的性能. 主要原因是这些类里面大多使用(失败-重试方式的)乐观锁而不是synchronized方式的悲观锁. 今天有时间跟踪了一下AtomicInteger的incrementAndGet的实现. 本人对并发编程也不是特别了解, 在这里就是做

fangchengimz的博客 344
上一篇: BOOST源码笔记(4) - thread_move_t概念
下一篇: BOOST源码笔记(6) - 无安全保证的随意转换~~~force_cast
daeees
博客等级 码龄19年 6粉丝 51原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值