<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://yhongq.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://yhongq.github.io/" rel="alternate" type="text/html" /><updated>2026-10-13T09:34:13+08:00</updated><id>https://yhongq.github.io/feed.xml</id><title type="html">溄</title><subtitle>HONG的个人博客</subtitle><author><name>溄</name><email>hongqiang.yan@qq.com</email></author><entry><title type="html">Redis黑马点评：秒杀券异步操作：从零理解消息队列与Lua脚本的完美结合</title><link href="https://yhongq.github.io/posts/redis-async-seckill/" rel="alternate" type="text/html" title="Redis黑马点评：秒杀券异步操作：从零理解消息队列与Lua脚本的完美结合" /><published>2026-06-04T00:00:00+08:00</published><updated>2026-06-04T00:00:00+08:00</updated><id>https://yhongq.github.io/posts/redis-async-seckill</id><content type="html" xml:base="https://yhongq.github.io/posts/redis-async-seckill/"><![CDATA[<h1 id="redis秒杀券异步操作从零理解消息队列与lua脚本的完美结合">Redis秒杀券异步操作：从零理解消息队列与Lua脚本的完美结合</h1>

<p>在高并发秒杀场景中，如何既保证系统的高性能，又确保数据的一致性？今天我们从学习者的角度，深入剖析一个完整的秒杀券异步处理方案，看看Redis、Lua脚本、消息队列、线程池等技术是如何协同工作的。</p>

<hr />

<h2 id="1-为什么需要异步处理">1. 为什么需要异步处理？</h2>

<p>在秒杀场景中，如果所有操作都在主线程同步完成，会面临以下问题：</p>

<ul>
  <li><strong>响应慢</strong>：每个请求都需要等待数据库操作完成，用户体验差</li>
  <li><strong>系统压力大</strong>：数据库连接被长时间占用，容易达到瓶颈</li>
  <li><strong>并发能力有限</strong>：同步处理限制了系统的吞吐量</li>
</ul>

<p><strong>异步处理的思路</strong>：主线程只做快速判断和入队操作，立即返回响应；订单创建等耗时操作由后台线程池慢慢处理。</p>

<hr />

<h2 id="2-整体架构设计">2. 整体架构设计</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>用户请求 → Lua脚本原子判断 → 入队 → 立即返回
                    ↓
              阻塞队列（消息队列）
                    ↓
              线程池（N个线程）
                    ↓
          分布式锁 + 数据库事务操作
</code></pre></div></div>

<p>这个设计的关键点：</p>
<ul>
  <li><strong>Redis Lua脚本</strong>：保证库存判断和订单记录的原子性</li>
  <li><strong>阻塞队列</strong>：作为消息队列，缓冲请求</li>
  <li><strong>线程池</strong>：异步处理订单创建</li>
  <li><strong>分布式锁</strong>：保证一人一单</li>
</ul>

<hr />

<h2 id="3-lua脚本redis原子操作的守护者">3. Lua脚本：Redis原子操作的守护者</h2>

<h3 id="31-为什么需要lua脚本">3.1 为什么需要Lua脚本？</h3>

<p>在秒杀场景中，我们需要同时做两件事：</p>
<ol>
  <li>判断库存是否充足</li>
  <li>判断用户是否已经下过单</li>
</ol>

<p>如果用普通的Redis命令分两步执行，在并发情况下会出现问题：</p>
<ul>
  <li>线程A检查库存充足</li>
  <li>线程B也检查库存充足</li>
  <li>两个线程都扣减库存</li>
  <li>结果库存变成负数，超卖！</li>
</ul>

<p><strong>Lua脚本的优势</strong>：Redis保证Lua脚本的执行是原子的，要么全部执行，要么都不执行。</p>

<h3 id="32-lua脚本实现解析">3.2 Lua脚本实现解析</h3>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- 获取优惠券ID和用户ID</span>
<span class="kd">local</span> <span class="n">voucherID</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="kd">local</span> <span class="n">userID</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>

<span class="c1">-- 构造Redis key</span>
<span class="kd">local</span> <span class="n">stockKey</span> <span class="o">=</span> <span class="s1">'seckill:stock:'</span> <span class="o">..</span> <span class="n">voucherID</span>
<span class="kd">local</span> <span class="n">orderKey</span> <span class="o">=</span> <span class="s1">'seckill:order:'</span> <span class="o">..</span> <span class="n">voucherID</span>

<span class="c1">-- 1. 判断库存是否充足</span>
<span class="kd">local</span> <span class="n">stock</span> <span class="o">=</span> <span class="nb">tonumber</span><span class="p">(</span><span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'GET'</span><span class="p">,</span> <span class="n">stockKey</span><span class="p">))</span>
<span class="k">if</span> <span class="n">stock</span> <span class="o">==</span> <span class="kc">nil</span> <span class="ow">or</span> <span class="n">stock</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="k">then</span>
    <span class="k">return</span> <span class="mi">1</span>  <span class="c1">-- 库存不足</span>
<span class="k">end</span>

<span class="c1">-- 2. 判断用户是否已经下单（一人一单）</span>
<span class="k">if</span><span class="p">(</span><span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'sismember'</span><span class="p">,</span> <span class="n">orderKey</span><span class="p">,</span> <span class="n">userID</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="k">then</span>
    <span class="k">return</span> <span class="mi">2</span>  <span class="c1">-- 用户已下单</span>
<span class="k">end</span>

<span class="c1">-- 3. 库存充足且用户未下单，执行原子操作</span>
<span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'incrby'</span><span class="p">,</span> <span class="n">stockKey</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>  <span class="c1">-- 库存减1</span>
<span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'sadd'</span><span class="p">,</span> <span class="n">orderKey</span><span class="p">,</span> <span class="n">userID</span><span class="p">)</span>  <span class="c1">-- 添加到订单集合</span>
<span class="k">return</span> <span class="mi">0</span>  <span class="c1">-- 成功</span>
</code></pre></div></div>

<p><strong>技术要点</strong>：</p>
<ul>
  <li><strong>返回值设计</strong>：0成功，1库存不足，2重复下单，便于Java代码判断</li>
  <li><strong>Set结构</strong>：<code class="language-plaintext highlighter-rouge">sismember</code>和<code class="language-plaintext highlighter-rouge">sadd</code>操作天然保证一人一单</li>
  <li><strong>原子性</strong>：整个脚本在Redis中单线程执行，不会出现并发问题</li>
</ul>

<h3 id="33-java中调用lua脚本">3.3 Java中调用Lua脚本</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 静态初始化Lua脚本</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">DefaultRedisScript</span><span class="o">&lt;</span><span class="nc">Long</span><span class="o">&gt;</span> <span class="no">SECKILL_SCRIPT</span><span class="o">;</span>
<span class="kd">static</span> <span class="o">{</span>
    <span class="no">SECKILL_SCRIPT</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultRedisScript</span><span class="o">&lt;&gt;();</span>
    <span class="no">SECKILL_SCRIPT</span><span class="o">.</span><span class="na">setLocation</span><span class="o">(</span><span class="k">new</span> <span class="nc">ClassPathResource</span><span class="o">(</span><span class="s">"seckill.lua"</span><span class="o">));</span>
    <span class="no">SECKILL_SCRIPT</span><span class="o">.</span><span class="na">setResultType</span><span class="o">(</span><span class="nc">Long</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// 执行Lua脚本</span>
<span class="nc">Long</span> <span class="n">result</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span>
    <span class="no">SECKILL_SCRIPT</span><span class="o">,</span>
    <span class="nc">Collections</span><span class="o">.</span><span class="na">emptyList</span><span class="o">(),</span>  <span class="c1">// 没有KEYS参数</span>
    <span class="n">voucherId</span><span class="o">.</span><span class="na">toString</span><span class="o">(),</span>      <span class="c1">// ARGV[1]</span>
    <span class="n">userID</span><span class="o">.</span><span class="na">toString</span><span class="o">()</span>          <span class="c1">// ARGV[2]</span>
<span class="o">);</span>
</code></pre></div></div>

<hr />

<h2 id="4-消息队列缓冲压力的利器">4. 消息队列：缓冲压力的利器</h2>

<h3 id="41-为什么选择blockingqueue">4.1 为什么选择BlockingQueue？</h3>

<h2 id="java的blockingqueue是一个线程安全的阻塞队列适合作为内存消息队">Java的<code class="language-plaintext highlighter-rouge">BlockingQueue</code>是一个线程安全的阻塞队列，适合作为内存消息队：</h2>
<blockquote>
  <p>缺点也很明显：存储在内存，当Java进程重启或关闭时，队列中的数据会丢失。如果需要持久化存储，需要额外的配置。</p>

  <p>并且内存容量有限，不能无限增长。会因为内存溢出而导致系统崩溃。所以，需要根据实际情况选择合适的队列容量。</p>
</blockquote>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 创建容量为1024*1024的阻塞队列</span>
<span class="kd">private</span> <span class="nc">BlockingQueue</span><span class="o">&lt;</span><span class="nc">VoucherOrder</span><span class="o">&gt;</span> <span class="n">voucherOrderBlockingQueue</span> <span class="o">=</span> 
    <span class="k">new</span> <span class="nc">ArrayBlockingQueue</span><span class="o">&lt;</span><span class="nc">VoucherOrder</span><span class="o">&gt;(</span><span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span><span class="o">);</span>
</code></pre></div></div>

<p><strong>BlockingQueue的特点</strong>：</p>
<ul>
  <li><strong>线程安全</strong>：多个线程可以安全地并发操作</li>
  <li><strong>阻塞机制</strong>：队列满时，<code class="language-plaintext highlighter-rouge">put()</code>会阻塞；队列空时，<code class="language-plaintext highlighter-rouge">take()</code>会阻塞</li>
  <li><strong>容量限制</strong>：防止内存溢出，超过容量会阻塞</li>
</ul>

<h3 id="42-入队操作">4.2 入队操作</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 创建订单对象</span>
<span class="nc">Long</span> <span class="n">orderID</span> <span class="o">=</span> <span class="n">redisIdWorker</span><span class="o">.</span><span class="na">nextId</span><span class="o">(</span><span class="s">"order"</span><span class="o">);</span>
<span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">VoucherOrder</span><span class="o">();</span>
<span class="n">voucherOrder</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">orderID</span><span class="o">);</span>
<span class="n">voucherOrder</span><span class="o">.</span><span class="na">setUserId</span><span class="o">(</span><span class="n">userID</span><span class="o">);</span>
<span class="n">voucherOrder</span><span class="o">.</span><span class="na">setVoucherId</span><span class="o">(</span><span class="n">voucherId</span><span class="o">);</span>

<span class="c1">// 放入队列（如果队列满会阻塞）</span>
<span class="n">voucherOrderBlockingQueue</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>
</code></pre></div></div>

<p><strong>设计考虑</strong>：使用<code class="language-plaintext highlighter-rouge">add()</code>而不是<code class="language-plaintext highlighter-rouge">put()</code>，如果队列满会抛出异常，避免无限等待。</p>

<hr />

<h2 id="5-线程池异步处理的核心">5. 线程池：异步处理的核心</h2>

<h3 id="51-线程池的创建与初始化">5.1 线程池的创建与初始化</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 创建固定大小为10的线程池</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">ExecutorService</span> <span class="no">SECKILL_EXECUTOR_SERVICE</span> <span class="o">=</span> 
    <span class="nc">Executors</span><span class="o">.</span><span class="na">newFixedThreadPool</span><span class="o">(</span><span class="mi">10</span><span class="o">);</span>

<span class="c1">// 项目启动时初始化</span>
<span class="nd">@PostConstruct</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">()</span> <span class="o">{</span>
    <span class="no">SECKILL_EXECUTOR_SERVICE</span><span class="o">.</span><span class="na">submit</span><span class="o">(</span><span class="k">new</span> <span class="nc">VocherOrderHandler</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>技术要点</strong>：</p>
<ul>
  <li><strong>@PostConstruct</strong>：在Spring Bean初始化完成后自动执行。 因为当前任务是异步处理， 所以需要提前启动线程池，确保第一个请求到来时线程池已就绪。</li>
  <li><strong>固定大小线程池</strong>：10个线程处理订单，避免资源耗尽</li>
  <li><strong>提前启动</strong>：确保第一个请求到来时线程池已就绪</li>
</ul>

<h3 id="52-runnable任务订单处理器">5.2 Runnable任务：订单处理器</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">class</span> <span class="nc">VocherOrderHandler</span> <span class="kd">implements</span> <span class="nc">Runnable</span> <span class="o">{</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">try</span> <span class="o">{</span>
                <span class="c1">// 从队列中取出订单（如果队列空会阻塞）</span>
                <span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span> <span class="o">=</span> <span class="n">voucherOrderBlockingQueue</span><span class="o">.</span><span class="na">take</span><span class="o">();</span>
                <span class="c1">// 处理订单</span>
                <span class="n">handlerVoucherOrder</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>
            <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"订单处理线程被中断"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li><strong>阻塞等待</strong>：<code class="language-plaintext highlighter-rouge">take()</code>方法在队列空时会阻塞，不浪费CPU</li>
  <li><strong>异常处理</strong>：捕获中断异常，保证线程不会意外退出</li>
</ul>

<hr />

<h2 id="6-分布式锁跨jvm的一人一单保证">6. 分布式锁：跨JVM的一人一单保证</h2>

<h3 id="61-为什么需要分布式锁">6.1 为什么需要分布式锁？</h3>

<p>在多服务器部署的情况下，<code class="language-plaintext highlighter-rouge">synchronized</code>只能保证单个JVM内的线程安全，无法解决跨服务器的一人一单问题。</p>

<p><strong>Redisson分布式锁</strong>：基于Redis实现的分布式锁，支持锁续命、可重入等特性。</p>

<h3 id="62-分布式锁的使用">6.2 分布式锁的使用</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">handlerVoucherOrder</span><span class="o">(</span><span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="n">voucherOrder</span><span class="o">.</span><span class="na">getUserId</span><span class="o">();</span>
    
    <span class="c1">// 1. 创建锁对象（按用户ID加锁）</span>
    <span class="nc">RLock</span> <span class="n">lock</span> <span class="o">=</span> <span class="n">redissonClient</span><span class="o">.</span><span class="na">getLock</span><span class="o">(</span><span class="s">"lock:order:"</span> <span class="o">+</span> <span class="n">userID</span><span class="o">);</span>
    
    <span class="c1">// 2. 尝试获取锁</span>
    <span class="kt">boolean</span> <span class="n">locked</span> <span class="o">=</span> <span class="n">lock</span><span class="o">.</span><span class="na">tryLock</span><span class="o">();</span>
    <span class="k">if</span> <span class="o">(!</span><span class="n">locked</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span><span class="o">+</span><span class="n">userID</span><span class="o">+</span><span class="s">"}下单失败，获取锁失败"</span><span class="o">);</span>
        <span class="k">return</span><span class="o">;</span>
    <span class="o">}</span>
    
    <span class="c1">// 3. 执行业务逻辑</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="n">proxy</span><span class="o">.</span><span class="na">createOrder</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span><span class="o">+</span><span class="n">userID</span><span class="o">+</span><span class="s">"}下单失败，创建订单失败"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
    <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
        <span class="c1">// 4. 释放锁</span>
        <span class="n">lock</span><span class="o">.</span><span class="na">unlock</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>技术要点</strong>：</p>
<ul>
  <li><strong>锁粒度</strong>：按用户ID加锁，不同用户互不影响</li>
  <li><strong>tryLock()</strong>：非阻塞获取锁，失败立即返回</li>
  <li><strong>finally释放</strong>：确保锁一定会被释放，避免死锁</li>
</ul>

<hr />

<h2 id="7-spring事务数据一致性的最后保障">7. Spring事务：数据一致性的最后保障</h2>

<h3 id="71-事务方法设计">7.1 事务方法设计</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Transactional</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">createOrder</span><span class="o">(</span><span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="n">voucherOrder</span><span class="o">.</span><span class="na">getUserId</span><span class="o">();</span>
    <span class="nc">Long</span> <span class="n">voucherId</span> <span class="o">=</span> <span class="n">voucherOrder</span><span class="o">.</span><span class="na">getVoucherId</span><span class="o">();</span>

    <span class="c1">// 1. 再次检查一人一单（双重检查）</span>
    <span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="n">query</span><span class="o">()</span>
        <span class="o">.</span><span class="na">eq</span><span class="o">(</span><span class="s">"user_id"</span><span class="o">,</span> <span class="n">userID</span><span class="o">)</span>
        <span class="o">.</span><span class="na">eq</span><span class="o">(</span><span class="s">"voucher_id"</span><span class="o">,</span> <span class="n">voucherId</span><span class="o">)</span>
        <span class="o">.</span><span class="na">count</span><span class="o">();</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">count</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span><span class="o">+</span><span class="n">userID</span><span class="o">+</span><span class="s">"}已购买过该优惠券"</span><span class="o">);</span>
        <span class="k">return</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="c1">// 2. 扣减库存（乐观锁）</span>
    <span class="kt">boolean</span> <span class="n">result</span> <span class="o">=</span> <span class="n">seckillVoucherService</span><span class="o">.</span><span class="na">update</span><span class="o">()</span>
        <span class="o">.</span><span class="na">setSql</span><span class="o">(</span><span class="s">"stock = stock - 1"</span><span class="o">)</span>
        <span class="o">.</span><span class="na">eq</span><span class="o">(</span><span class="s">"voucher_id"</span><span class="o">,</span> <span class="n">voucherId</span><span class="o">)</span>
        <span class="o">.</span><span class="na">gt</span><span class="o">(</span><span class="s">"stock"</span><span class="o">,</span> <span class="mi">0</span><span class="o">)</span>  <span class="c1">// 乐观锁：库存大于0才更新</span>
        <span class="o">.</span><span class="na">update</span><span class="o">();</span>
    <span class="k">if</span> <span class="o">(!</span><span class="n">result</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span><span class="o">+</span><span class="n">userID</span><span class="o">+</span><span class="s">"}下单失败，优惠券已售罄"</span><span class="o">);</span>
        <span class="k">return</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="c1">// 3. 保存订单</span>
    <span class="n">save</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>技术要点</strong>：</p>
<ul>
  <li><strong>@Transactional</strong>：保证数据库操作的原子性</li>
  <li><strong>双重检查</strong>：虽然Redis Lua脚本已经检查过，但数据库层再检查一次更安全</li>
  <li><strong>乐观锁</strong>：<code class="language-plaintext highlighter-rouge">gt("stock", 0)</code>防止数据库层面的超卖</li>
  <li><strong>事务回滚</strong>：任何一步失败，整个操作回滚</li>
</ul>

<h3 id="72-为什么需要aop代理">7.2 为什么需要AOP代理？</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 获取代理对象</span>
<span class="n">proxy</span> <span class="o">=</span> <span class="o">(</span><span class="nc">IVoucherOrderService</span><span class="o">)</span> <span class="nc">AopContext</span><span class="o">.</span><span class="na">currentProxy</span><span class="o">();</span>
</code></pre></div></div>

<p><strong>原因</strong>：<code class="language-plaintext highlighter-rouge">createOrder</code>方法有<code class="language-plaintext highlighter-rouge">@Transactional</code>注解，需要通过代理对象调用才能让事务生效。</p>

<p><strong>Spring AOP原理</strong>：</p>
<ul>
  <li>Spring为Bean创建代理对象</li>
  <li>代理对象在方法调用前后织入事务逻辑</li>
  <li>直接调用<code class="language-plaintext highlighter-rouge">this.createOrder()</code>不会经过代理，事务失效</li>
</ul>

<hr />

<h2 id="8-完整流程梳理">8. 完整流程梳理</h2>

<h3 id="81-主线程流程快速响应">8.1 主线程流程（快速响应）</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 用户发起秒杀请求
2. 执行Lua脚本：
   - 检查库存
   - 检查是否重复下单
   - 原子扣库存+记录订单
3. 创建订单对象
4. 放入阻塞队列
5. 立即返回订单ID给用户
</code></pre></div></div>

<p><strong>时间复杂度</strong>：O(1)，只涉及Redis操作，毫秒级响应。</p>

<h3 id="82-后台线程流程异步处理">8.2 后台线程流程（异步处理）</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 线程池中的线程从队列取出订单
2. 获取分布式锁（按用户ID）
3. 调用createOrder方法（事务生效）：
   - 检查数据库中是否已下单
   - 扣减数据库库存（乐观锁）
   - 保存订单记录
4. 释放分布式锁
</code></pre></div></div>

<p><strong>特点</strong>：不阻塞用户请求，慢慢处理数据库操作。</p>

<h2 id="11-完整源码">11. 完整源码</h2>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.hmdp.service.impl</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">lombok.extern.slf4j.Slf4j</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.core.io.ClassPathResource</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.dto.Result</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.entity.VoucherOrder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.mapper.VoucherOrderMapper</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.service.ISeckillVoucherService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.service.IVoucherOrderService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.extension.service.impl.ServiceImpl</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.utils.RedisIdWorker</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.utils.UserHolder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.redisson.api.RLock</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.redisson.api.RedissonClient</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.aop.framework.AopContext</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.StringRedisTemplate</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.script.DefaultRedisScript</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.stereotype.Service</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.transaction.annotation.Transactional</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">javax.annotation.PostConstruct</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.annotation.Resource</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Collections</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.concurrent.ArrayBlockingQueue</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.concurrent.BlockingQueue</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.concurrent.ExecutorService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.concurrent.Executors</span><span class="o">;</span>

<span class="nd">@Slf4j</span>
<span class="nd">@Service</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">VoucherOrderServiceImpl</span> <span class="kd">extends</span> <span class="nc">ServiceImpl</span><span class="o">&lt;</span><span class="nc">VoucherOrderMapper</span><span class="o">,</span> <span class="nc">VoucherOrder</span><span class="o">&gt;</span> 
        <span class="kd">implements</span> <span class="nc">IVoucherOrderService</span> <span class="o">{</span>
    
    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">StringRedisTemplate</span> <span class="n">stringRedisTemplate</span><span class="o">;</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">RedisIdWorker</span> <span class="n">redisIdWorker</span><span class="o">;</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">ISeckillVoucherService</span> <span class="n">seckillVoucherService</span><span class="o">;</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">RedissonClient</span> <span class="n">redissonClient</span><span class="o">;</span>

    <span class="c1">// Lua脚本</span>
    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">DefaultRedisScript</span><span class="o">&lt;</span><span class="nc">Long</span><span class="o">&gt;</span> <span class="no">SECKILL_SCRIPT</span><span class="o">;</span>
    <span class="kd">static</span> <span class="o">{</span>
        <span class="no">SECKILL_SCRIPT</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultRedisScript</span><span class="o">&lt;&gt;();</span>
        <span class="no">SECKILL_SCRIPT</span><span class="o">.</span><span class="na">setLocation</span><span class="o">(</span><span class="k">new</span> <span class="nc">ClassPathResource</span><span class="o">(</span><span class="s">"seckill.lua"</span><span class="o">));</span>
        <span class="no">SECKILL_SCRIPT</span><span class="o">.</span><span class="na">setResultType</span><span class="o">(</span><span class="nc">Long</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="c1">// 阻塞队列（消息队列）</span>
    <span class="kd">private</span> <span class="nc">BlockingQueue</span><span class="o">&lt;</span><span class="nc">VoucherOrder</span><span class="o">&gt;</span> <span class="n">voucherOrderBlockingQueue</span> <span class="o">=</span> 
        <span class="k">new</span> <span class="nc">ArrayBlockingQueue</span><span class="o">&lt;</span><span class="nc">VoucherOrder</span><span class="o">&gt;(</span><span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span><span class="o">);</span>

    <span class="c1">// 线程池</span>
    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">ExecutorService</span> <span class="no">SECKILL_EXECUTOR_SERVICE</span> <span class="o">=</span> 
        <span class="nc">Executors</span><span class="o">.</span><span class="na">newFixedThreadPool</span><span class="o">(</span><span class="mi">10</span><span class="o">);</span>

    <span class="c1">// 初始化</span>
    <span class="nd">@PostConstruct</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">()</span> <span class="o">{</span>
        <span class="no">SECKILL_EXECUTOR_SERVICE</span><span class="o">.</span><span class="na">submit</span><span class="o">(</span><span class="k">new</span> <span class="nc">VocherOrderHandler</span><span class="o">());</span>
    <span class="o">}</span>

    <span class="c1">// 订单处理线程</span>
    <span class="kd">private</span> <span class="kd">class</span> <span class="nc">VocherOrderHandler</span> <span class="kd">implements</span> <span class="nc">Runnable</span> <span class="o">{</span>
        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
            <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">try</span> <span class="o">{</span>
                    <span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span> <span class="o">=</span> <span class="n">voucherOrderBlockingQueue</span><span class="o">.</span><span class="na">take</span><span class="o">();</span>
                    <span class="n">handlerVoucherOrder</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>
                <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                    <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"订单处理线程被中断"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
                <span class="o">}</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="c1">// 代理对象</span>
    <span class="kd">private</span> <span class="nc">IVoucherOrderService</span> <span class="n">proxy</span><span class="o">;</span>

    <span class="c1">// 处理订单</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">handlerVoucherOrder</span><span class="o">(</span><span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="n">voucherOrder</span><span class="o">.</span><span class="na">getUserId</span><span class="o">();</span>
        <span class="nc">RLock</span> <span class="n">lock</span> <span class="o">=</span> <span class="n">redissonClient</span><span class="o">.</span><span class="na">getLock</span><span class="o">(</span><span class="s">"lock:order:"</span> <span class="o">+</span> <span class="n">userID</span><span class="o">);</span>
        <span class="kt">boolean</span> <span class="n">locked</span> <span class="o">=</span> <span class="n">lock</span><span class="o">.</span><span class="na">tryLock</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">locked</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span><span class="o">+</span><span class="n">userID</span><span class="o">+</span><span class="s">"}下单失败，获取锁失败"</span><span class="o">);</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="n">proxy</span><span class="o">.</span><span class="na">createOrder</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span><span class="o">+</span><span class="n">userID</span><span class="o">+</span><span class="s">"}下单失败，创建订单失败"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
            <span class="n">lock</span><span class="o">.</span><span class="na">unlock</span><span class="o">();</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="c1">// 秒杀入口</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">Result</span> <span class="nf">seckillVoucher</span><span class="o">(</span><span class="nc">Long</span> <span class="n">voucherId</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="nc">UserHolder</span><span class="o">.</span><span class="na">getUser</span><span class="o">().</span><span class="na">getId</span><span class="o">();</span>
        
        <span class="c1">// 执行Lua脚本</span>
        <span class="nc">Long</span> <span class="n">result</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span>
            <span class="no">SECKILL_SCRIPT</span><span class="o">,</span>
            <span class="nc">Collections</span><span class="o">.</span><span class="na">emptyList</span><span class="o">(),</span>
            <span class="n">voucherId</span><span class="o">.</span><span class="na">toString</span><span class="o">(),</span>
            <span class="n">userID</span><span class="o">.</span><span class="na">toString</span><span class="o">()</span>
        <span class="o">);</span>
        
        <span class="kt">int</span> <span class="n">r</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="na">intValue</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">r</span> <span class="o">==</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"库存不足"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">r</span> <span class="o">==</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"您已下单过"</span><span class="o">);</span>
        <span class="o">}</span>

        <span class="c1">// 创建订单并放入队列</span>
        <span class="nc">Long</span> <span class="n">orderID</span> <span class="o">=</span> <span class="n">redisIdWorker</span><span class="o">.</span><span class="na">nextId</span><span class="o">(</span><span class="s">"order"</span><span class="o">);</span>
        <span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">VoucherOrder</span><span class="o">();</span>
        <span class="n">voucherOrder</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">orderID</span><span class="o">);</span>
        <span class="n">voucherOrder</span><span class="o">.</span><span class="na">setUserId</span><span class="o">(</span><span class="n">userID</span><span class="o">);</span>
        <span class="n">voucherOrder</span><span class="o">.</span><span class="na">setVoucherId</span><span class="o">(</span><span class="n">voucherId</span><span class="o">);</span>
        <span class="n">voucherOrderBlockingQueue</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>

        <span class="n">proxy</span> <span class="o">=</span> <span class="o">(</span><span class="nc">IVoucherOrderService</span><span class="o">)</span> <span class="nc">AopContext</span><span class="o">.</span><span class="na">currentProxy</span><span class="o">();</span>
        <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">orderID</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="c1">// 创建订单（事务方法）</span>
    <span class="nd">@Transactional</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">createOrder</span><span class="o">(</span><span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="n">voucherOrder</span><span class="o">.</span><span class="na">getUserId</span><span class="o">();</span>
        <span class="nc">Long</span> <span class="n">voucherId</span> <span class="o">=</span> <span class="n">voucherOrder</span><span class="o">.</span><span class="na">getVoucherId</span><span class="o">();</span>

        <span class="c1">// 检查是否已下单</span>
        <span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="n">query</span><span class="o">()</span>
            <span class="o">.</span><span class="na">eq</span><span class="o">(</span><span class="s">"user_id"</span><span class="o">,</span> <span class="n">userID</span><span class="o">)</span>
            <span class="o">.</span><span class="na">eq</span><span class="o">(</span><span class="s">"voucher_id"</span><span class="o">,</span> <span class="n">voucherId</span><span class="o">)</span>
            <span class="o">.</span><span class="na">count</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">count</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span><span class="o">+</span><span class="n">userID</span><span class="o">+</span><span class="s">"}已购买过该优惠券"</span><span class="o">);</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="c1">// 扣减库存（乐观锁）</span>
        <span class="kt">boolean</span> <span class="n">result</span> <span class="o">=</span> <span class="n">seckillVoucherService</span><span class="o">.</span><span class="na">update</span><span class="o">()</span>
            <span class="o">.</span><span class="na">setSql</span><span class="o">(</span><span class="s">"stock = stock - 1"</span><span class="o">)</span>
            <span class="o">.</span><span class="na">eq</span><span class="o">(</span><span class="s">"voucher_id"</span><span class="o">,</span> <span class="n">voucherId</span><span class="o">)</span>
            <span class="o">.</span><span class="na">gt</span><span class="o">(</span><span class="s">"stock"</span><span class="o">,</span> <span class="mi">0</span><span class="o">)</span>
            <span class="o">.</span><span class="na">update</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">result</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span><span class="o">+</span><span class="n">userID</span><span class="o">+</span><span class="s">"}下单失败，优惠券已售罄"</span><span class="o">);</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="c1">// 保存订单</span>
        <span class="n">save</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>Lua脚本（seckill.lua）</strong>：</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">voucherID</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="kd">local</span> <span class="n">userID</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>

<span class="kd">local</span> <span class="n">stockKey</span> <span class="o">=</span> <span class="s1">'seckill:stock:'</span> <span class="o">..</span> <span class="n">voucherID</span>
<span class="kd">local</span> <span class="n">orderKey</span> <span class="o">=</span> <span class="s1">'seckill:order:'</span> <span class="o">..</span> <span class="n">voucherID</span>

<span class="kd">local</span> <span class="n">stock</span> <span class="o">=</span> <span class="nb">tonumber</span><span class="p">(</span><span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'GET'</span><span class="p">,</span> <span class="n">stockKey</span><span class="p">))</span>
<span class="k">if</span> <span class="n">stock</span> <span class="o">==</span> <span class="kc">nil</span> <span class="ow">or</span> <span class="n">stock</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="k">then</span>
    <span class="k">return</span> <span class="mi">1</span>
<span class="k">end</span>

<span class="k">if</span><span class="p">(</span><span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'sismember'</span><span class="p">,</span> <span class="n">orderKey</span><span class="p">,</span> <span class="n">userID</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="k">then</span>
    <span class="k">return</span> <span class="mi">2</span>
<span class="k">end</span>

<span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'incrby'</span><span class="p">,</span> <span class="n">stockKey</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'sadd'</span><span class="p">,</span> <span class="n">orderKey</span><span class="p">,</span> <span class="n">userID</span><span class="p">)</span>
<span class="k">return</span> <span class="mi">0</span>
</code></pre></div></div>

<hr />

<p>希望这篇博客能帮助你理解秒杀系统的设计原理和实现细节！</p>]]></content><author><name>溄</name><email>hongqiang.yan@qq.com</email></author><category term="学习笔记" /><category term="Redis" /><category term="消息队列" /><category term="Lua 脚本" /><category term="异步处理" /><category term="线程池" /><category term="Java" /><summary type="html"><![CDATA[Redis 黑马点评：秒杀券异步操作，从零理解消息队列与 Lua 脚本的完美结合。]]></summary></entry><entry><title type="html">Redis黑马点评：秒杀券与Redis消息队列操作</title><link href="https://yhongq.github.io/posts/redis-stream-seckill/" rel="alternate" type="text/html" title="Redis黑马点评：秒杀券与Redis消息队列操作" /><published>2026-06-04T00:00:00+08:00</published><updated>2026-06-04T00:00:00+08:00</updated><id>https://yhongq.github.io/posts/redis-stream-seckill</id><content type="html" xml:base="https://yhongq.github.io/posts/redis-stream-seckill/"><![CDATA[<h1 id="redis-stream-消息队列实现异步秒杀lua-脚本--消费者组实战">Redis Stream 消息队列实现异步秒杀——Lua 脚本 + 消费者组实战</h1>

<p>上一篇文章我们讨论了用 <code class="language-plaintext highlighter-rouge">synchronized</code> + Spring 事务实现「一人一单」秒杀。那个方案能跑，但有一个明显的短板：<strong>用户得等着服务器把订单写进数据库，接口才返回结果。</strong> 当秒杀流量冲上来，数据库写入这个慢操作会堵住大量 Tomcat 线程，整个系统响应越来越慢。</p>

<p>怎么解决？答案是 <strong>把下单动作异步化</strong>。用户请求过来，服务器只做最核心的资格校验（库存够不够、有没有重复下单），校验通过后扔一条消息到队列，立刻告诉用户”抢到了”。真正的数据库写入交给后台线程慢慢消化。</p>

<p>用什么当消息队列？RabbitMQ、RocketMQ、Kafka 都能干，但这套项目本身强依赖 Redis，再引入一个中间件太重了。正好 Redis 5.0 之后自带了 <strong>Stream</strong> 数据结构，天生就是个轻量级消息队列。不需要额外部署，直接用。</p>

<p><a href="https://www.bilibili.com/video/BV1cr4y1671t?vd_source=3495715132f8b407fde241a86af24789&amp;spm_id_from=333.788.player.switch&amp;p=58">实战篇-11.RedisStream消息队列-实现异步秒杀_哔哩哔哩_bilibili</a></p>

<hr />

<h2 id="1-redis-stream-是什么">1. Redis Stream 是什么？</h2>

<p>用一句话理解：<strong>Redis Stream 就是 Redis 内置的一个链表，但它比 List 多了消费者组、消息确认、消息回溯这些正经消息队列才有的能力。</strong></p>

<p>先看几个核心概念，不用记，跟着代码走一遍自然就懂了：</p>

<table>
  <thead>
    <tr>
      <th>概念</th>
      <th>大白话解释</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Stream</strong></td>
      <td>消息队列本身，一条 Stream 就是一个队列，比如 <code class="language-plaintext highlighter-rouge">stream.orders</code></td>
    </tr>
    <tr>
      <td><strong>消息</strong></td>
      <td>队列里的每一条数据，由若干键值对组成</td>
    </tr>
    <tr>
      <td><strong>消费者组（Consumer Group）</strong></td>
      <td>一组消费者共享消费进度。同一条消息组内只会被一个消费者处理</td>
    </tr>
    <tr>
      <td><strong>消费者（Consumer）</strong></td>
      <td>组里的一个具体干活的人，比如 <code class="language-plaintext highlighter-rouge">c1</code></td>
    </tr>
    <tr>
      <td><strong>XADD</strong></td>
      <td>往 Stream 尾部追加一条消息</td>
    </tr>
    <tr>
      <td><strong>XREADGROUP</strong></td>
      <td>消费者组模式读消息，读完后消息会进入 Pending 列表</td>
    </tr>
    <tr>
      <td><strong>XACK</strong></td>
      <td>消息处理完了，确认一下，从 Pending 列表移除</td>
    </tr>
    <tr>
      <td><strong>Pending 列表</strong></td>
      <td>被读走但还没确认的消息暂存区，消费者挂了可以从这里捞回来重试</td>
    </tr>
  </tbody>
</table>

<p>整个流程画出来就三步：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>用户请求 → Lua脚本(XADD入队) → 返回"抢到了"
                                    ↓
                         后台线程(XREADGROUP读消息 → 写数据库 → XACK确认)
</code></pre></div></div>

<hr />

<h2 id="2-为什么要把资格校验写在-lua-里">2. 为什么要把资格校验写在 Lua 里？</h2>

<p>资格校验要做三件事：<strong>查库存 → 查是否重复下单 → 扣库存 + 标记已下单</strong>。这三步必须是原子操作。</p>

<p>假设不用 Lua，用 Java 代码一步步调 Redis：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 危险写法：三步分开，中间可能被其他线程插入</span>
<span class="nc">String</span> <span class="n">stock</span> <span class="o">=</span> <span class="n">redis</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"seckill:stock:10"</span><span class="o">);</span>  <span class="c1">// 读到库存=1</span>
<span class="nc">Boolean</span> <span class="n">exists</span> <span class="o">=</span> <span class="n">redis</span><span class="o">.</span><span class="na">sismember</span><span class="o">(</span><span class="s">"seckill:order:10"</span><span class="o">,</span> <span class="n">userId</span><span class="o">);</span> <span class="c1">// 没下过单</span>
<span class="c1">// 此时另一个线程也读到了库存=1，也判断没下过单</span>
<span class="n">redis</span><span class="o">.</span><span class="na">decr</span><span class="o">(</span><span class="s">"seckill:stock:10"</span><span class="o">);</span>    <span class="c1">// 两个线程都执行了扣减 → 超卖</span>
<span class="n">redis</span><span class="o">.</span><span class="na">sadd</span><span class="o">(</span><span class="s">"seckill:order:10"</span><span class="o">,</span> <span class="n">userId</span><span class="o">);</span>
</code></pre></div></div>

<p>三步之间有空隙，并发下必然出问题。而 <strong>Lua 脚本在 Redis 服务端执行，整个脚本执行期间 Redis 是单线程的，天然原子性</strong>。这正是我们要的效果。</p>

<hr />

<h2 id="3-seckilllua-逐行拆解">3. seckill.lua 逐行拆解</h2>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- 从 Java 传过来的三个参数</span>
<span class="kd">local</span> <span class="n">voucherId</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>   <span class="c1">-- 优惠券ID</span>
<span class="kd">local</span> <span class="n">userId</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>      <span class="c1">-- 用户ID</span>
<span class="kd">local</span> <span class="n">orderId</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span>     <span class="c1">-- 预生成的订单ID</span>

<span class="c1">-- Redis 中的两个 Key</span>
<span class="kd">local</span> <span class="n">stockKey</span> <span class="o">=</span> <span class="s1">'seckill:stock:'</span> <span class="o">..</span> <span class="n">voucherId</span>   <span class="c1">-- 库存，比如 seckill:stock:10</span>
<span class="kd">local</span> <span class="n">orderKey</span> <span class="o">=</span> <span class="s1">'seckill:order:'</span> <span class="o">..</span> <span class="n">voucherId</span>   <span class="c1">-- 已下单用户集合（Set）</span>

<span class="c1">-- 第一步：查库存</span>
<span class="kd">local</span> <span class="n">stock</span> <span class="o">=</span> <span class="nb">tonumber</span><span class="p">(</span><span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'GET'</span><span class="p">,</span> <span class="n">stockKey</span><span class="p">))</span>
<span class="k">if</span> <span class="n">stock</span> <span class="o">==</span> <span class="kc">nil</span> <span class="ow">or</span> <span class="n">stock</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="k">then</span>
    <span class="k">return</span> <span class="mi">1</span>   <span class="c1">-- 返回值 1：库存不足</span>
<span class="k">end</span>

<span class="c1">-- 第二步：查是否重复下单（一人一单）</span>
<span class="k">if</span><span class="p">(</span><span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'sismember'</span><span class="p">,</span> <span class="n">orderKey</span><span class="p">,</span> <span class="n">userId</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="k">then</span>
    <span class="k">return</span> <span class="mi">2</span>   <span class="c1">-- 返回值 2：已经下过单了</span>
<span class="k">end</span>

<span class="c1">-- 第三步：扣库存 + 记录用户 + 发消息，一气呵成</span>
<span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'incrby'</span><span class="p">,</span> <span class="n">stockKey</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>      <span class="c1">-- 库存 -1</span>
<span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'sadd'</span><span class="p">,</span> <span class="n">orderKey</span><span class="p">,</span> <span class="n">userId</span><span class="p">)</span>     <span class="c1">-- 用户加入已购集合</span>

<span class="c1">-- 核心：往 Redis Stream 里塞一条消息，* 表示让 Redis 自动生成消息 ID</span>
<span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'XADD'</span><span class="p">,</span> <span class="s1">'stream.orders'</span><span class="p">,</span> <span class="s1">'*'</span><span class="p">,</span>
    <span class="s1">'userId'</span><span class="p">,</span> <span class="n">userId</span><span class="p">,</span>
    <span class="s1">'id'</span><span class="p">,</span> <span class="n">orderId</span><span class="p">,</span>
    <span class="s1">'voucherId'</span><span class="p">,</span> <span class="n">voucherId</span><span class="p">)</span>
<span class="k">return</span> <span class="mi">0</span>   <span class="c1">-- 返回值 0：一切顺利</span>
</code></pre></div></div>

<p>返回值约定：<code class="language-plaintext highlighter-rouge">0</code> 抢到、<code class="language-plaintext highlighter-rouge">1</code> 库存没了、<code class="language-plaintext highlighter-rouge">2</code> 已经买过了。Java 代码拿到返回值就知道该给用户什么提示。</p>

<blockquote>
  <p><strong>知识点</strong>：<code class="language-plaintext highlighter-rouge">redis.call('XADD', key, 'MAXLEN', '~', 10000, '*', ...)</code> 可以给 Stream 设置一个最大长度，超过时自动裁剪老消息，防止 Redis 内存无限增长。当前示例为了简洁没加，实际生产建议加上。</p>
</blockquote>

<hr />

<h2 id="4-java-端请求入口快进快出">4. Java 端：请求入口——快进快出</h2>

<p>用户请求到了 Controller，最终进入 <code class="language-plaintext highlighter-rouge">VoucherOrderServiceImpl.seckillVoucher()</code>。这个方法的逻辑非常简单：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Result</span> <span class="nf">seckillVoucher</span><span class="o">(</span><span class="nc">Long</span> <span class="n">voucherId</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="nc">UserHolder</span><span class="o">.</span><span class="na">getUser</span><span class="o">().</span><span class="na">getId</span><span class="o">();</span>
    <span class="nc">Long</span> <span class="n">orderID</span> <span class="o">=</span> <span class="n">redisIdWorker</span><span class="o">.</span><span class="na">nextId</span><span class="o">(</span><span class="s">"order"</span><span class="o">);</span>  <span class="c1">// 全局唯一ID生成器</span>

    <span class="c1">// 执行 Lua 脚本，返回值 0/1/2</span>
    <span class="nc">Long</span> <span class="n">result</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span>
            <span class="no">SECKILL_SCRIPT</span><span class="o">,</span>
            <span class="nc">Collections</span><span class="o">.</span><span class="na">emptyList</span><span class="o">(),</span>     <span class="c1">// KEYS 为空，参数全放 ARGV</span>
            <span class="n">voucherId</span><span class="o">.</span><span class="na">toString</span><span class="o">(),</span>
            <span class="n">userID</span><span class="o">.</span><span class="na">toString</span><span class="o">(),</span>
            <span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">orderID</span><span class="o">));</span>

    <span class="kt">int</span> <span class="n">r</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="na">intValue</span><span class="o">();</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">r</span> <span class="o">==</span> <span class="mi">1</span><span class="o">)</span> <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"库存不足"</span><span class="o">);</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">r</span> <span class="o">==</span> <span class="mi">2</span><span class="o">)</span> <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"您已下单过"</span><span class="o">);</span>

    <span class="c1">// 关键：获取代理对象，供子线程使用（Spring 事务需要通过代理调用）</span>
    <span class="n">proxy</span> <span class="o">=</span> <span class="o">(</span><span class="nc">IVoucherOrderService</span><span class="o">)</span> <span class="nc">AopContext</span><span class="o">.</span><span class="na">currentProxy</span><span class="o">();</span>
    <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">orderID</span><span class="o">);</span>    <span class="c1">// 立刻返回，用户不用等数据库写入</span>
<span class="o">}</span>
</code></pre></div></div>

<p>用户看到 <code class="language-plaintext highlighter-rouge">Result.ok</code> 就说明抢到了，后续的数据库操作对他完全透明。整个接口耗时就是一次 Lua 脚本的网络往返，毫秒级。</p>

<hr />

<h2 id="5-消费者后台线程怎么消化消息">5. 消费者：后台线程怎么消化消息</h2>

<p>消息扔进 Stream 只是第一步，谁来消费它？答案是<strong>项目启动时就拉起一个后台线程，死循环从 Stream 里读消息</strong>。</p>

<h3 id="51-线程启动时机">5.1 线程启动时机</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">ExecutorService</span> <span class="no">SECKILL_EXECUTOR_SERVICE</span> <span class="o">=</span>
        <span class="nc">Executors</span><span class="o">.</span><span class="na">newFixedThreadPool</span><span class="o">(</span><span class="mi">10</span><span class="o">);</span>

<span class="nd">@PostConstruct</span>       <span class="c1">// Spring Bean 初始化完成后自动执行</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">()</span> <span class="o">{</span>
    <span class="no">SECKILL_EXECUTOR_SERVICE</span><span class="o">.</span><span class="na">submit</span><span class="o">(</span><span class="k">new</span> <span class="nc">VocherOrderHandler</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">@PostConstruct</code> 保证 Bean 一初始化，消费线程就开始跑，不用等第一个用户请求。</p>

<h3 id="52-消息拉取xreadgroup">5.2 消息拉取：XREADGROUP</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o">&lt;</span><span class="nc">MapRecord</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForStream</span><span class="o">().</span><span class="na">read</span><span class="o">(</span>
        <span class="nc">Consumer</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="s">"g1"</span><span class="o">,</span> <span class="s">"c1"</span><span class="o">),</span>              <span class="c1">// 消费者组 g1，消费者 c1</span>
        <span class="nc">StreamReadOptions</span><span class="o">.</span><span class="na">empty</span><span class="o">()</span>
                <span class="o">.</span><span class="na">count</span><span class="o">(</span><span class="mi">1</span><span class="o">)</span>                        <span class="c1">// 一次取一条</span>
                <span class="o">.</span><span class="na">block</span><span class="o">(</span><span class="nc">Duration</span><span class="o">.</span><span class="na">ofSeconds</span><span class="o">(</span><span class="mi">2</span><span class="o">)),</span>   <span class="c1">// 队列为空时阻塞 2 秒，不是忙轮询</span>
        <span class="nc">StreamOffset</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">queueName</span><span class="o">,</span> <span class="nc">ReadOffset</span><span class="o">.</span><span class="na">lastConsumed</span><span class="o">())</span>  <span class="c1">// 从未消费的消息开始读</span>
<span class="o">);</span>
</code></pre></div></div>

<p>几个参数解释：</p>

<ul>
  <li><strong>Consumer.from(“g1”, “c1”)</strong>：消费者组叫 <code class="language-plaintext highlighter-rouge">g1</code>，组内的消费者叫 <code class="language-plaintext highlighter-rouge">c1</code>。如果部署多台机器，每台起一个不同的消费者名（c1、c2、c3），同一条消息只会被一台机器处理，天然负载均衡。</li>
  <li><strong>count(1)</strong>：每次读一条，处理完再读下一条。如果业务快也可以加大。</li>
  <li><strong>block(Duration.ofSeconds(2))</strong>：阻塞等待 2 秒。如果队列为空，不是立刻返回空然后疯狂循环（那样会打满 CPU），而是挂起等待，最多等 2 秒。这 2 秒内如果有新消息进来立刻返回，没有就超时返回空，进入下一轮循环。</li>
  <li><strong>ReadOffset.lastConsumed()</strong>：只读没被消费过的消息。已确认的消息不会再读。</li>
</ul>

<h3 id="53-读到消息后做什么">5.3 读到消息后做什么</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">MapRecord</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">mapRecord</span> <span class="o">=</span> <span class="n">list</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">map</span> <span class="o">=</span> <span class="n">mapRecord</span><span class="o">.</span><span class="na">getValue</span><span class="o">();</span>

<span class="c1">// BeanUtil.fillBeanWithMap 把 Map 自动映射成 VoucherOrder 对象</span>
<span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span> <span class="o">=</span> <span class="nc">BeanUtil</span><span class="o">.</span><span class="na">fillBeanWithMap</span><span class="o">(</span>
        <span class="n">map</span><span class="o">,</span> <span class="k">new</span> <span class="nc">VoucherOrder</span><span class="o">(),</span> <span class="kc">true</span><span class="o">);</span>

<span class="c1">// 交给实际处理方法</span>
<span class="n">handlerVoucherOrder</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>

<span class="c1">// 处理成功 → ACK 确认</span>
<span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForStream</span><span class="o">().</span><span class="na">acknowledge</span><span class="o">(</span>
        <span class="n">queueName</span><span class="o">,</span> <span class="s">"g1"</span><span class="o">,</span> <span class="n">mapRecord</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">acknowledge</code> 就是告诉 Redis：”这条消息我处理完了，你可以从 Pending 列表里删掉它。”</p>

<h3 id="54-消息处理失败怎么办pending-兜底">5.4 消息处理失败怎么办——Pending 兜底</h3>

<p><code class="language-plaintext highlighter-rouge">handlerVoucherOrder</code> 里如果抛了异常，消息不会 ACK，会一直留在 Pending 列表。所以 <code class="language-plaintext highlighter-rouge">catch</code> 块里调了 <code class="language-plaintext highlighter-rouge">handlePendingOrder()</code>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">handlePendingOrder</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">List</span><span class="o">&lt;</span><span class="nc">MapRecord</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForStream</span><span class="o">().</span><span class="na">read</span><span class="o">(</span>
                <span class="nc">Consumer</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="s">"g1"</span><span class="o">,</span> <span class="s">"c1"</span><span class="o">),</span>
                <span class="nc">StreamReadOptions</span><span class="o">.</span><span class="na">empty</span><span class="o">().</span><span class="na">count</span><span class="o">(</span><span class="mi">1</span><span class="o">),</span>
                <span class="nc">StreamOffset</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">queueName</span><span class="o">,</span> <span class="nc">ReadOffset</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="s">"0"</span><span class="o">))</span>  <span class="c1">// "0"=从 Pending 列表开头读</span>
        <span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">list</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">list</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>
            <span class="k">break</span><span class="o">;</span>  <span class="c1">// Pending 清空，退出</span>
        <span class="o">}</span>
        <span class="c1">// 同样处理 → 同样 ACK</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>这里 <code class="language-plaintext highlighter-rouge">ReadOffset.from("0")</code> 就是关键区别：它不从新消息开始读，而是从 <strong>Pending 列表的最早一条</strong> 开始读。这样之前处理失败的消息会被重新捞出来再试一次。</p>

<blockquote>
  <p><strong>Pending 机制的意义</strong>：如果消费者在写数据库的时候挂了（进程崩溃、机器断电），消息没来得及 ACK，等服务重启后 Pending 处理会把这条消息捞回来重新消费。这就是消息队列的”至少一次”投递保证。</p>
</blockquote>

<h3 id="55-handlervoucherorder-内部为什么还要加锁">5.5 handlerVoucherOrder 内部为什么还要加锁</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">handlerVoucherOrder</span><span class="o">(</span><span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="n">voucherOrder</span><span class="o">.</span><span class="na">getUserId</span><span class="o">();</span>
    <span class="nc">RLock</span> <span class="n">lock</span> <span class="o">=</span> <span class="n">redissonClient</span><span class="o">.</span><span class="na">getLock</span><span class="o">(</span><span class="s">"lock:order:"</span> <span class="o">+</span> <span class="n">userID</span><span class="o">);</span>
    <span class="kt">boolean</span> <span class="n">locked</span> <span class="o">=</span> <span class="n">lock</span><span class="o">.</span><span class="na">tryLock</span><span class="o">();</span>
    <span class="k">if</span> <span class="o">(!</span><span class="n">locked</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span><span class="o">;</span>  <span class="c1">// 同一用户的消息正在被另一个消费者处理，跳过</span>
    <span class="o">}</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="n">proxy</span><span class="o">.</span><span class="na">createOrder</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>
    <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
        <span class="n">lock</span><span class="o">.</span><span class="na">unlock</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Lua 脚本已经保证了「一人一单」的资格校验，为什么这里还要加锁？因为 <strong>资格校验和数据库写入是分离的</strong>。极端情况下，同一个用户在短时间内发起了两次请求，两次 Lua 脚本执行时都查不到已下单记录（都还没来得及写数据库），两条消息都会入队。后台消费时，第一条写库成功，第二条如果不加锁直接写库，就会产生重复订单。</p>

<p>所以这里用 Redisson 分布式锁兜底，同一用户 ID 的消息串行处理，第二条进来时发现第一条还没处理完（锁没释放），直接跳过。<code class="language-plaintext highlighter-rouge">createOrder</code> 内部还有一次数据库查询做最终判断，三重保障。</p>

<hr />

<h2 id="6-整体架构回顾">6. 整体架构回顾</h2>

<p>把整个流程串起来看一遍：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>用户点击秒杀
    │
    ▼
Controller → Service.seckillVoucher()
    │
    ├── 预生成订单ID（RedisIdWorker）
    │
    ├── 执行 seckill.lua（Redis 服务端原子执行）
    │       ├── 库存检查
    │       ├── 重复下单检查
    │       ├── 扣库存 + 标记用户
    │       └── XADD → stream.orders
    │
    └── 立刻返回 Result.ok(orderID) 给用户
            │
            │  【异步，用户无感】
            ▼
  后台线程 VocherOrderHandler
            │
            ├── XREADGROUP 拉取消息（阻塞2秒）
            │
            ├── 解析消息 → VoucherOrder 对象
            │
            ├── handlerVoucherOrder()
            │       ├── Redisson 分布式锁（用户级）
            │       └── proxy.createOrder()（事务写库）
            │
            └── XACK 确认消息
</code></pre></div></div>

<p><strong>这样做的好处</strong>：</p>

<ul>
  <li>用户请求响应极快（只跑 Lua 脚本，不碰数据库）</li>
  <li>数据库写入压力被消息队列削峰填谷</li>
  <li>Lua 原子性保证库存扣减和一人一单判断不会并发出错</li>
  <li>Pending + ACK 机制保证消息不丢</li>
  <li>消费者组支持多实例部署，横向扩容简单</li>
</ul>

<hr />

<h2 id="7-小结">7. 小结</h2>

<p>从 <code class="language-plaintext highlighter-rouge">synchronized</code> 到 Redis Stream，秒杀方案经过了两次关键升级：</p>

<ol>
  <li><strong>加锁 → Lua 脚本</strong>：把资格校验从 Java 层搬到 Redis 层，利用 Redis 单线程实现真正的原子操作，避免了「判断和扣减之间有间隙」的并发问题。</li>
  <li><strong>同步写库 → 消息队列异步</strong>：用户不用等数据库，接口响应从”数据库事务耗时”变成”一次 Redis 网络往返”，吞吐量提升了一个数量级。</li>
</ol>

<p>Redis Stream 作为消息队列虽然没有 RabbitMQ 那么功能齐全，但在<strong>已经依赖 Redis 的项目里</strong>是一个非常自然的选择，代码量少、部署零成本，对于异步秒杀这种场景完全够用。</p>

<hr />

<h2 id="8-源码附上">8. 源码附上</h2>

<h3 id="81-seckilllua">8.1 seckill.lua</h3>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">--  获取优惠券ID（优惠卷编号）</span>
<span class="kd">local</span> <span class="n">voucherId</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="c1">-- 用户ID</span>
<span class="kd">local</span> <span class="n">userId</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
<span class="c1">-- 订单ID （创建的订单编号）</span>
<span class="kd">local</span> <span class="n">orderId</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span>

<span class="c1">-- 操作数据</span>
<span class="c1">-- 库存Key</span>
<span class="kd">local</span> <span class="n">stockKey</span> <span class="o">=</span> <span class="s1">'seckill:stock:'</span> <span class="o">..</span> <span class="n">voucherId</span>
<span class="c1">-- 订单Key</span>
<span class="kd">local</span> <span class="n">orderKey</span> <span class="o">=</span> <span class="s1">'seckill:order:'</span> <span class="o">..</span> <span class="n">voucherId</span>

<span class="c1">-- 脚本业务</span>
<span class="c1">-- 判断库存是否充足，不足则返回失败</span>
<span class="kd">local</span> <span class="n">stock</span> <span class="o">=</span> <span class="nb">tonumber</span><span class="p">(</span><span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'GET'</span><span class="p">,</span> <span class="n">stockKey</span><span class="p">))</span>
<span class="k">if</span> <span class="n">stock</span> <span class="o">==</span> <span class="kc">nil</span> <span class="ow">or</span> <span class="n">stock</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="k">then</span>
    <span class="k">return</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="c1">-- 库存充足，判断用户是否已经下单（限制一人一单）</span>
<span class="k">if</span><span class="p">(</span><span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'sismember'</span><span class="p">,</span> <span class="n">orderKey</span><span class="p">,</span> <span class="n">userId</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="k">then</span>
<span class="c1">--     标识用户已经下过单</span>
    <span class="k">return</span> <span class="mi">2</span>
<span class="k">end</span>

<span class="c1">-- 用户没有下过单，且库存充足，需要减库存，添加订单</span>
<span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'incrby'</span><span class="p">,</span> <span class="n">stockKey</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'sadd'</span><span class="p">,</span> <span class="n">orderKey</span><span class="p">,</span> <span class="n">userId</span><span class="p">)</span>

<span class="c1">--  新增消息Redis Stream 队列模式，将任务放入队列中</span>
<span class="c1">--  消息内容：用户ID、订单ID、优惠券ID</span>
<span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'XADD'</span><span class="p">,</span> <span class="s1">'stream.orders'</span><span class="p">,</span><span class="s1">'*'</span><span class="p">,</span><span class="s1">'userId'</span><span class="p">,</span><span class="n">userId</span><span class="p">,</span><span class="s1">'id'</span><span class="p">,</span><span class="n">orderId</span><span class="p">,</span><span class="s1">'voucherId'</span><span class="p">,</span><span class="n">voucherId</span><span class="p">)</span>
<span class="k">return</span> <span class="mi">0</span>
</code></pre></div></div>

<h3 id="82-ivoucherorderservicejava">8.2 IVoucherOrderService.java</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.hmdp.service</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">com.hmdp.dto.Result</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.entity.VoucherOrder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.extension.service.IService</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">IVoucherOrderService</span> <span class="kd">extends</span> <span class="nc">IService</span><span class="o">&lt;</span><span class="nc">VoucherOrder</span><span class="o">&gt;</span> <span class="o">{</span>

    <span class="nc">Result</span> <span class="nf">seckillVoucher</span><span class="o">(</span><span class="nc">Long</span> <span class="n">voucherId</span><span class="o">);</span>

    <span class="kt">void</span> <span class="nf">createOrder</span><span class="o">(</span><span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="83-voucherordercontrollerjava">8.3 VoucherOrderController.java</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.hmdp.controller</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">com.hmdp.dto.Result</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.service.IVoucherOrderService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.web.bind.annotation.PathVariable</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.web.bind.annotation.PostMapping</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.web.bind.annotation.RequestMapping</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.web.bind.annotation.RestController</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">javax.annotation.Resource</span><span class="o">;</span>

<span class="nd">@RestController</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/voucher-order"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">VoucherOrderController</span> <span class="o">{</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">IVoucherOrderService</span> <span class="n">voucherOrderService</span><span class="o">;</span>

    <span class="nd">@PostMapping</span><span class="o">(</span><span class="s">"seckill/{id}"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="nc">Result</span> <span class="nf">seckillVoucher</span><span class="o">(</span><span class="nd">@PathVariable</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="nc">Long</span> <span class="n">voucherId</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">voucherOrderService</span><span class="o">.</span><span class="na">seckillVoucher</span><span class="o">(</span><span class="n">voucherId</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="84-voucherorderserviceimpljava完整版含-stream-消费者">8.4 VoucherOrderServiceImpl.java（完整版，含 Stream 消费者）</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.hmdp.service.impl</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">cn.hutool.core.bean.BeanUtil</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">lombok.extern.slf4j.Slf4j</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.core.io.ClassPathResource</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.dto.Result</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.entity.VoucherOrder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.mapper.VoucherOrderMapper</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.service.ISeckillVoucherService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.service.IVoucherOrderService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.extension.service.impl.ServiceImpl</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.utils.RedisIdWorker</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.utils.UserHolder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.redisson.api.RLock</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.redisson.api.RedissonClient</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.aop.framework.AopContext</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.connection.stream.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.StringRedisTemplate</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.script.DefaultRedisScript</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.stereotype.Service</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.transaction.annotation.Transactional</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">javax.annotation.PostConstruct</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.annotation.Resource</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.time.Duration</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Collections</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Map</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.concurrent.ExecutorService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.concurrent.Executors</span><span class="o">;</span>

<span class="nd">@Slf4j</span>
<span class="nd">@Service</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">VoucherOrderServiceImpl</span>
        <span class="kd">extends</span> <span class="nc">ServiceImpl</span><span class="o">&lt;</span><span class="nc">VoucherOrderMapper</span><span class="o">,</span> <span class="nc">VoucherOrder</span><span class="o">&gt;</span>
        <span class="kd">implements</span> <span class="nc">IVoucherOrderService</span> <span class="o">{</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">StringRedisTemplate</span> <span class="n">stringRedisTemplate</span><span class="o">;</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">RedisIdWorker</span> <span class="n">redisIdWorker</span><span class="o">;</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">ISeckillVoucherService</span> <span class="n">seckillVoucherService</span><span class="o">;</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">RedissonClient</span> <span class="n">redissonClient</span><span class="o">;</span>

    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">DefaultRedisScript</span><span class="o">&lt;</span><span class="nc">Long</span><span class="o">&gt;</span> <span class="no">SECKILL_SCRIPT</span><span class="o">;</span>
    <span class="kd">static</span> <span class="o">{</span>
        <span class="no">SECKILL_SCRIPT</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultRedisScript</span><span class="o">&lt;&gt;();</span>
        <span class="no">SECKILL_SCRIPT</span><span class="o">.</span><span class="na">setLocation</span><span class="o">(</span><span class="k">new</span> <span class="nc">ClassPathResource</span><span class="o">(</span><span class="s">"seckill.lua"</span><span class="o">));</span>
        <span class="no">SECKILL_SCRIPT</span><span class="o">.</span><span class="na">setResultType</span><span class="o">(</span><span class="nc">Long</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">ExecutorService</span> <span class="no">SECKILL_EXECUTOR_SERVICE</span> <span class="o">=</span>
            <span class="nc">Executors</span><span class="o">.</span><span class="na">newFixedThreadPool</span><span class="o">(</span><span class="mi">10</span><span class="o">);</span>

    <span class="nd">@PostConstruct</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">()</span> <span class="o">{</span>
        <span class="no">SECKILL_EXECUTOR_SERVICE</span><span class="o">.</span><span class="na">submit</span><span class="o">(</span><span class="k">new</span> <span class="nc">VocherOrderHandler</span><span class="o">());</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kd">class</span> <span class="nc">VocherOrderHandler</span> <span class="kd">implements</span> <span class="nc">Runnable</span> <span class="o">{</span>
        <span class="nc">String</span> <span class="n">queueName</span> <span class="o">=</span> <span class="s">"stream.orders"</span><span class="o">;</span>

        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
            <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">try</span> <span class="o">{</span>
                    <span class="c1">// 1. 从 Redis Stream 队列中取出订单消息</span>
                    <span class="nc">List</span><span class="o">&lt;</span><span class="nc">MapRecord</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForStream</span><span class="o">().</span><span class="na">read</span><span class="o">(</span>
                            <span class="nc">Consumer</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="s">"g1"</span><span class="o">,</span> <span class="s">"c1"</span><span class="o">),</span>
                            <span class="nc">StreamReadOptions</span><span class="o">.</span><span class="na">empty</span><span class="o">().</span><span class="na">count</span><span class="o">(</span><span class="mi">1</span><span class="o">).</span><span class="na">block</span><span class="o">(</span><span class="nc">Duration</span><span class="o">.</span><span class="na">ofSeconds</span><span class="o">(</span><span class="mi">2</span><span class="o">)),</span>
                            <span class="nc">StreamOffset</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">queueName</span><span class="o">,</span> <span class="nc">ReadOffset</span><span class="o">.</span><span class="na">lastConsumed</span><span class="o">())</span>
                    <span class="o">);</span>
                    <span class="c1">// 2. 判断消息是否获取成功</span>
                    <span class="k">if</span> <span class="o">(</span><span class="n">list</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">list</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>
                        <span class="k">continue</span><span class="o">;</span>
                    <span class="o">}</span>
                    <span class="c1">// 3. 解析消息内容</span>
                    <span class="nc">MapRecord</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">mapRecord</span> <span class="o">=</span> <span class="n">list</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
                    <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">map</span> <span class="o">=</span> <span class="n">mapRecord</span><span class="o">.</span><span class="na">getValue</span><span class="o">();</span>
                    <span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span> <span class="o">=</span> <span class="nc">BeanUtil</span><span class="o">.</span><span class="na">fillBeanWithMap</span><span class="o">(</span>
                            <span class="n">map</span><span class="o">,</span> <span class="k">new</span> <span class="nc">VoucherOrder</span><span class="o">(),</span> <span class="kc">true</span><span class="o">);</span>

                    <span class="c1">// 4. 处理订单</span>
                    <span class="n">handlerVoucherOrder</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>

                    <span class="c1">// 5. ACK 确认，Redis 从 Pending 中移除该消息</span>
                    <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForStream</span><span class="o">().</span><span class="na">acknowledge</span><span class="o">(</span>
                            <span class="n">queueName</span><span class="o">,</span> <span class="s">"g1"</span><span class="o">,</span> <span class="n">mapRecord</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
                <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                    <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户下单失败，创建订单异常"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
                    <span class="n">handlePendingOrder</span><span class="o">();</span>
                <span class="o">}</span>
            <span class="o">}</span>
        <span class="o">}</span>

        <span class="kd">private</span> <span class="kt">void</span> <span class="nf">handlePendingOrder</span><span class="o">()</span> <span class="o">{</span>
            <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">try</span> <span class="o">{</span>
                    <span class="c1">// 从 Pending 列表中取出未确认的消息（"0" 表示从 Pending 开头读取）</span>
                    <span class="nc">List</span><span class="o">&lt;</span><span class="nc">MapRecord</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForStream</span><span class="o">().</span><span class="na">read</span><span class="o">(</span>
                            <span class="nc">Consumer</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="s">"g1"</span><span class="o">,</span> <span class="s">"c1"</span><span class="o">),</span>
                            <span class="nc">StreamReadOptions</span><span class="o">.</span><span class="na">empty</span><span class="o">().</span><span class="na">count</span><span class="o">(</span><span class="mi">1</span><span class="o">),</span>
                            <span class="nc">StreamOffset</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">queueName</span><span class="o">,</span> <span class="nc">ReadOffset</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="s">"0"</span><span class="o">))</span>
                    <span class="o">);</span>
                    <span class="k">if</span> <span class="o">(</span><span class="n">list</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">list</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>
                        <span class="k">break</span><span class="o">;</span>
                    <span class="o">}</span>
                    <span class="nc">MapRecord</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">mapRecord</span> <span class="o">=</span> <span class="n">list</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
                    <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">map</span> <span class="o">=</span> <span class="n">mapRecord</span><span class="o">.</span><span class="na">getValue</span><span class="o">();</span>
                    <span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span> <span class="o">=</span> <span class="nc">BeanUtil</span><span class="o">.</span><span class="na">fillBeanWithMap</span><span class="o">(</span>
                            <span class="n">map</span><span class="o">,</span> <span class="k">new</span> <span class="nc">VoucherOrder</span><span class="o">(),</span> <span class="kc">true</span><span class="o">);</span>

                    <span class="n">handlerVoucherOrder</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>

                    <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForStream</span><span class="o">().</span><span class="na">acknowledge</span><span class="o">(</span>
                            <span class="n">queueName</span><span class="o">,</span> <span class="s">"g1"</span><span class="o">,</span> <span class="n">mapRecord</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
                <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                    <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户下单失败，创建订单异常"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
                <span class="o">}</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="nc">IVoucherOrderService</span> <span class="n">proxy</span><span class="o">;</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">handlerVoucherOrder</span><span class="o">(</span><span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="n">voucherOrder</span><span class="o">.</span><span class="na">getUserId</span><span class="o">();</span>
        <span class="nc">RLock</span> <span class="n">lock</span> <span class="o">=</span> <span class="n">redissonClient</span><span class="o">.</span><span class="na">getLock</span><span class="o">(</span><span class="s">"lock:order:"</span> <span class="o">+</span> <span class="n">userID</span><span class="o">);</span>
        <span class="kt">boolean</span> <span class="n">locked</span> <span class="o">=</span> <span class="n">lock</span><span class="o">.</span><span class="na">tryLock</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">locked</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span> <span class="o">+</span> <span class="n">userID</span> <span class="o">+</span> <span class="s">"}下单失败，获取锁失败"</span><span class="o">);</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="n">proxy</span><span class="o">.</span><span class="na">createOrder</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span> <span class="o">+</span> <span class="n">userID</span> <span class="o">+</span> <span class="s">"}下单失败，创建订单失败"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
            <span class="n">lock</span><span class="o">.</span><span class="na">unlock</span><span class="o">();</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">Result</span> <span class="nf">seckillVoucher</span><span class="o">(</span><span class="nc">Long</span> <span class="n">voucherId</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="nc">UserHolder</span><span class="o">.</span><span class="na">getUser</span><span class="o">().</span><span class="na">getId</span><span class="o">();</span>
        <span class="nc">Long</span> <span class="n">orderID</span> <span class="o">=</span> <span class="n">redisIdWorker</span><span class="o">.</span><span class="na">nextId</span><span class="o">(</span><span class="s">"order"</span><span class="o">);</span>

        <span class="c1">// 执行 Lua 脚本：0=有资格 1=库存不足 2=已下单</span>
        <span class="nc">Long</span> <span class="n">result</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span>
                <span class="no">SECKILL_SCRIPT</span><span class="o">,</span>
                <span class="nc">Collections</span><span class="o">.</span><span class="na">emptyList</span><span class="o">(),</span>
                <span class="n">voucherId</span><span class="o">.</span><span class="na">toString</span><span class="o">(),</span>
                <span class="n">userID</span><span class="o">.</span><span class="na">toString</span><span class="o">(),</span>
                <span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">orderID</span><span class="o">));</span>

        <span class="kt">int</span> <span class="n">r</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="na">intValue</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">r</span> <span class="o">==</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"库存不足"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">r</span> <span class="o">==</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"您已下单过"</span><span class="o">);</span>
        <span class="o">}</span>

        <span class="n">proxy</span> <span class="o">=</span> <span class="o">(</span><span class="nc">IVoucherOrderService</span><span class="o">)</span> <span class="nc">AopContext</span><span class="o">.</span><span class="na">currentProxy</span><span class="o">();</span>
        <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">orderID</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="nd">@Transactional</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">createOrder</span><span class="o">(</span><span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="n">voucherOrder</span><span class="o">.</span><span class="na">getUserId</span><span class="o">();</span>
        <span class="nc">Long</span> <span class="n">voucherId</span> <span class="o">=</span> <span class="n">voucherOrder</span><span class="o">.</span><span class="na">getVoucherId</span><span class="o">();</span>

        <span class="c1">// 二次检查：防止极端情况下的重复订单</span>
        <span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="n">query</span><span class="o">().</span><span class="na">eq</span><span class="o">(</span><span class="s">"user_id"</span><span class="o">,</span> <span class="n">userID</span><span class="o">).</span><span class="na">eq</span><span class="o">(</span><span class="s">"voucher_id"</span><span class="o">,</span> <span class="n">voucherId</span><span class="o">).</span><span class="na">count</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">count</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span> <span class="o">+</span> <span class="n">userID</span> <span class="o">+</span> <span class="s">"}已购买过该优惠卷"</span><span class="o">);</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="c1">// 扣减库存（乐观锁）</span>
        <span class="kt">boolean</span> <span class="n">result</span> <span class="o">=</span> <span class="n">seckillVoucherService</span><span class="o">.</span><span class="na">update</span><span class="o">()</span>
                <span class="o">.</span><span class="na">setSql</span><span class="o">(</span><span class="s">"stock = stock - 1"</span><span class="o">)</span>
                <span class="o">.</span><span class="na">eq</span><span class="o">(</span><span class="s">"voucher_id"</span><span class="o">,</span> <span class="n">voucherId</span><span class="o">)</span>
                <span class="o">.</span><span class="na">gt</span><span class="o">(</span><span class="s">"stock"</span><span class="o">,</span> <span class="mi">0</span><span class="o">)</span>
                <span class="o">.</span><span class="na">update</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">result</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span> <span class="o">+</span> <span class="n">userID</span> <span class="o">+</span> <span class="s">"}下单失败，优惠卷已售罄"</span><span class="o">);</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="c1">// 保存订单</span>
        <span class="n">save</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<hr />

<blockquote>
  <p><strong>提示</strong>：运行此代码前，请确保：</p>

  <ul>
    <li>Redis 5.0 或以上版本（Stream 是 5.0 引入的）</li>
    <li>启动类上配置了 <code class="language-plaintext highlighter-rouge">@EnableAspectJAutoProxy(exposeProxy = true)</code></li>
    <li>已在 Redis 中手动创建消费者组：<code class="language-plaintext highlighter-rouge">XGROUP CREATE stream.orders g1 0 MKSTREAM</code>（或代码中自动创建）</li>
    <li><code class="language-plaintext highlighter-rouge">seckill.lua</code> 放在 <code class="language-plaintext highlighter-rouge">src/main/resources</code> 目录下</li>
  </ul>
</blockquote>

<hr />

<p>希望这篇博客能帮你理清「Redis Stream 异步秒杀」的整个链路。从同步阻塞到异步削峰，从 Java 锁到 Lua 原子操作，每一步都是面对真实并发场景时不得不做的取舍。如有疑问，欢迎交流讨论！
<em>（内容由AI生成，仅供参考）</em></p>]]></content><author><name>溄</name><email>hongqiang.yan@qq.com</email></author><category term="学习笔记" /><category term="Redis" /><category term="消息队列" /><category term="Lua 脚本" /><category term="异步处理" /><category term="线程池" /><category term="Java" /><summary type="html"><![CDATA[Redis 黑马点评：秒杀券与 Redis 消息队列（Stream）操作实践。]]></summary></entry><entry><title type="html">Redis黑马课程学习</title><link href="https://yhongq.github.io/posts/redis-heima-course/" rel="alternate" type="text/html" title="Redis黑马课程学习" /><published>2026-05-31T00:00:00+08:00</published><updated>2026-05-31T00:00:00+08:00</updated><id>https://yhongq.github.io/posts/redis-heima-course</id><content type="html" xml:base="https://yhongq.github.io/posts/redis-heima-course/"><![CDATA[<h1 id="redis黑马课程学习">Redis黑马课程学习</h1>

<p>本课程介绍学习Redis的基本概念、数据结构、操作命令、集群部署等。</p>

<h2 id="1-redis介绍">1. Redis介绍</h2>

<p>Redis是一个基于内存的键值存储数据库，用于存储和检索数据。它支持多种数据结构，如字符串、列表、哈希、集合等。</p>

<ul>
  <li>支持数据持久化。</li>
</ul>

<p><strong>NoSQL数据库</strong> ：是一种非关系型数据库，它不使用关系型数据库的表和行结构，而是使用键值对存储数据。
<img src="/assets/images/posts/redis-heima-course/1779432758589.webp" alt="" /></p>

<h2 id="2-redis-常用命令">2. Redis 常用命令</h2>

<h3 id="21-redis-数据结构">2.1 Redis 数据结构</h3>

<ol>
  <li>Redis是键值对存储数据库，每个键对应一个值。</li>
  <li>Redis支持多种数据结构，如字符串、列表、哈希、集合等。</li>
  <li>不同的数据结构有不同的操作命令。
<img src="/assets/images/posts/redis-heima-course/1779518651818.webp" alt="" /></li>
</ol>

<h3 id="22-redis-常用命令">2.2 Redis 常用命令</h3>

<ol>
  <li>
    <p>KEYS  命令：用于查看键名。</p>

    <ul>
      <li>语法：KEY [pattern]</li>
      <li>示例：KEY * # 查看所有键名</li>
      <li>示例：KEY <em>key</em> # 查看包含”key”的键名</li>
    </ul>
  </li>
  <li>help [command] 命令：用于查看指定命令的详细信息。</li>
  <li>
    <p>del 命令：用于删除指定键。</p>

    <ul>
      <li>语法：del [key]</li>
      <li>示例：del key1 key2 # 删除key1和key2</li>
    </ul>
  </li>
  <li>
    <p>exist 命令：用于检查键是否存在。</p>

    <ul>
      <li>语法：exists [key]</li>
      <li>返回值：1表示存在，0表示不存在</li>
    </ul>
  </li>
  <li>
    <p>expire 命令：用于设置键的过期时间,有效期到期后键将自动删除。</p>

    <ul>
      <li>语法：expire [key] [seconds]</li>
      <li>示例：expire key1 60 # 设置key1的过期时间为60秒</li>
      <li>返回值：1表示设置成功，0表示设置失败</li>
    </ul>
  </li>
  <li>
    <p>ttl 命令：用于查看键的过期时间。</p>

    <ul>
      <li>语法：ttl [key]</li>
      <li>示例：ttl key1 # 查看key1的过期时间</li>
      <li>返回值：-1表示永不过期，秒为单位</li>
    </ul>
  </li>
</ol>

<h3 id="23-string-类型value-为字符串命令">2.3 String 类型（Value 为字符串）命令</h3>

<ol>
  <li>
    <p>SET 命令：用于设置键值对。</p>

    <ul>
      <li>语法：SET [key] [value]</li>
      <li>示例：SET key1 value1 # 设置key1的值为value1</li>
      <li>返回值：OK表示设置成功</li>
    </ul>
  </li>
  <li>
    <p>GET 命令：用于获取键值对。</p>

    <ul>
      <li>语法：GET [key]</li>
      <li>示例：GET key1 # 获取key1的值</li>
      <li>返回值：value1</li>
    </ul>
  </li>
  <li>
    <p>MSET 命令：用于设置多个键值对。</p>

    <ul>
      <li>语法：MSET [key] [value] …</li>
      <li>示例：MSET key1 value1 key2 value2 # 设置key1和key2的值</li>
      <li>返回值：OK表示设置成功</li>
    </ul>
  </li>
  <li>
    <p>MGET 命令：用于获取多个键值对。</p>

    <ul>
      <li>语法：MGET [key] …</li>
      <li>示例：MGET key1 key2 # 获取key1和key2的值</li>
      <li>返回值：value1 value2</li>
    </ul>
  </li>
  <li>
    <p>INCR 命令：用于将键的值增加1。（仅对数值型键值对有效，其他类型键会报错。）</p>

    <ul>
      <li>语法：INCR [key]</li>
      <li>示例：INCR key1 # 将key1的值增加1</li>
    </ul>
  </li>
  <li>
    <p>INCRBY 命令：用于将键的值增加指定的增量。</p>

    <ul>
      <li>语法：INCRBY [key] [increment]</li>
      <li>示例：INCRBY key1 5 # 将key1的值增加5</li>
    </ul>
  </li>
  <li>
    <p>DECR 命令：用于将键的值减少1。（仅对数值型键值对有效，其他类型键会报错。）</p>

    <ul>
      <li>语法：DECR [key]</li>
      <li>示例：DECR key1 # 将key1的值减少1</li>
    </ul>
  </li>
  <li>
    <p>INCRBYFLOAT 命令：用于将键的值增加指定的浮点数增量。</p>

    <ul>
      <li>语法：INCRBYFLOAT [key] [increment]</li>
      <li>示例：INCRBYFLOAT key1 5.0 # 将key1的值增加5.0</li>
    </ul>
  </li>
  <li>
    <p>SETNX 命令：用于设置键值对，仅当键不存在时才设置。</p>

    <ul>
      <li>语法：SETNX [key] [value]</li>
      <li>示例：SETNX key1 value1 # 如果key1不存在，设置key1的值为value1</li>
      <li>返回值：1表示设置成功，0表示键已存在</li>
    </ul>
  </li>
  <li>
    <p>SETEX 命令：用于设置键值对，并设置过期时间。</p>

    <ul>
      <li>语法：SEEX [key] [seconds] [value]</li>
      <li>示例：SEEX key1 60 value1 # 设置key1的值为value1，过期时间为60秒</li>
      <li>返回值：OK表示设置成功</li>
    </ul>
  </li>
</ol>

<hr />

<blockquote>
  <p>Redis没有表结构，不存在唯一索引，因此在键值对上对键进行<strong>层级结构命名</strong>，多个单词间用”:”分隔。</p>

  <p>示例：user:123:info</p>

  <p>项目：业务名：类型：键值</p>
</blockquote>

<hr />

<h3 id="24-hash-类型value-为哈希表命令">2.4 Hash 类型（Value 为哈希表）命令</h3>

<ul>
  <li>Hash 类型，Value是一个无序字典集合，类似Java中的HashMap。</li>
  <li>因为值为哈希表，所以Hash类型支持键值对的存储和检索。</li>
</ul>

<ol>
  <li>
    <p>HSET 命令：用于设置哈希表的键值对。</p>

    <ul>
      <li>语法：HSET [key] [field] [value]</li>
      <li>示例：HSET key1 field1 value1 # 设置key1的哈希表字段field1的值为value1</li>
      <li>返回值：OK表示设置成功</li>
    </ul>
  </li>
  <li>
    <p>HGET 命令：用于获取哈希表的键值对。</p>

    <ul>
      <li>语法：HGET [key] [field]</li>
      <li>示例：HGET key1 field1 # 获取key1的哈希表字段field1的值</li>
      <li>返回值：value1</li>
    </ul>
  </li>
  <li>
    <p>HGETALL 命令：用于获取哈希表的所有键值对。</p>

    <ul>
      <li>语法：HGETALL [key]</li>
      <li>示例：HGETALL key1 # 获取key1的所有哈希表键值对</li>
      <li>返回值：field1:value1 field2:value2 …</li>
    </ul>
  </li>
  <li>
    <p>HDEL 命令：用于删除哈希表的键值对。</p>

    <ul>
      <li>语法：HDEL [key] [field] …</li>
      <li>示例：HDEL key1 field1 field2 # 删除key1的哈希表字段field1和field2</li>
      <li>返回值：OK表示删除成功</li>
    </ul>
  </li>
  <li>
    <p>HMSET 命令：用于设置多个哈希表的键值对。</p>

    <ul>
      <li>语法：HMSET [key] [field] [value] …</li>
      <li>示例：HMSET key1 field1 value1 field2 value2 # 设置key1的哈希表字段field1和field2的值</li>
      <li>返回值：OK表示设置成功</li>
    </ul>
  </li>
  <li>
    <p>HMGET 命令：用于获取多个哈希表的键值对。</p>

    <ul>
      <li>语法：HMGET [key] [field] …</li>
      <li>示例：HMGET key1 field1 field2 # 获取key1的哈希表字段field1和field2的值</li>
      <li>返回值：value1 value2</li>
    </ul>
  </li>
  <li>
    <p>HKEYS 命令：用于获取哈希表的所有键。</p>

    <ul>
      <li>语法：HKEYS [key]</li>
      <li>示例：HKEYS key1 # 获取key1的所有哈希表字段</li>
      <li>返回值：field1 field2 …</li>
    </ul>
  </li>
  <li>
    <p>HVALS 命令：用于获取哈希表的所有值。</p>

    <ul>
      <li>语法：HVALS [key]</li>
      <li>示例：HVALS key1 # 获取key1的所有哈希表字段值</li>
      <li>返回值：value1 value2 …</li>
    </ul>
  </li>
  <li>
    <p>HINCRBY 命令：用于将哈希表的键值对增加指定的增量。</p>

    <ul>
      <li>语法：HINCRBY [key] [field] [increment]</li>
      <li>示例：HINCRBY key1 field1 5 # 将key1的哈希表字段field1的值增加5</li>
      <li>返回值：value1</li>
    </ul>
  </li>
  <li>
    <p>HSETNX 命令：用于设置哈希表的键值对，仅当键不存在时才设置。</p>

    <ul>
      <li>语法：HSETNX [key] [field] [value]</li>
      <li>示例：HSETNX key1 field1 value1 # 如果key1的哈希表字段field1不存在，设置key1的哈希表字段field1的值为value1</li>
      <li>返回值：1表示设置成功，0表示键已存在</li>
    </ul>
  </li>
</ol>

<h3 id="25-list-类型value-为列表命令">2.5 List 类型（Value 为列表）命令</h3>

<ul>
  <li>List 类型，Value是一个有序列表，类似Java中的LinkedList,可视为双向链表。</li>
  <li>特点是：有序、元素可重复、插入和删除快、查询效率低。</li>
</ul>

<ol>
  <li>
    <p>LPUSH 命令：用于将元素插入到列表的左端。</p>

    <ul>
      <li>语法：LPUSH [key] [value]</li>
      <li>示例：LPUSH key1 value1 # 将value1插入到列表key1的左端</li>
      <li>返回值：列表的长度</li>
    </ul>
  </li>
  <li>
    <p>RPUSH 命令：用于将元素插入到列表的右端。</p>

    <ul>
      <li>语法：RPUSH [key] [value]</li>
      <li>示例：RPUSH key1 value1 # 将value1插入到列表key1的右端</li>
      <li>返回值：列表的长度</li>
    </ul>
  </li>
  <li>
    <p>LPOP 命令：用于从列表的左端弹出元素。<strong>如果列表为空，返回NULL</strong>。</p>

    <ul>
      <li>语法：LPOP [key]</li>
      <li>示例：LPOP key1 # 从列表key1的左端弹出元素</li>
      <li>返回值：value1</li>
    </ul>
  </li>
  <li>
    <p>RPOP 命令：用于从列表的右端弹出元素。<strong>如果列表为空，返回NULL</strong></p>

    <ul>
      <li>语法：RPOP [key]</li>
      <li>示例：RPOP key1 # 从列表key1的右端弹出元素</li>
      <li>返回值：value1</li>
    </ul>
  </li>
  <li>
    <p>LRANGE 命令：用于获取列表的指定范围内的元素。</p>

    <ul>
      <li>语法：LRANGE [key] [start] [end]</li>
      <li>示例：LRANGE key1 0 1 # 获取列表key1的第0个元素到第1个元素</li>
      <li>返回值：value1 value2</li>
    </ul>
  </li>
  <li>
    <p>LLEN 命令：用于获取列表的长度。</p>

    <ul>
      <li>语法：LLEN [key]</li>
      <li>示例：LLEN key1 # 获取列表key1的长度</li>
      <li>返回值：2</li>
    </ul>
  </li>
  <li>
    <p>BLPOP 命令：用于从列表的左端弹出元素，<strong>若列表为空则阻塞等待</strong>。</p>

    <ul>
      <li>语法：BLPOP [key] [timeout]</li>
      <li>示例：BLPOP key1 60 # 从列表key1的左端弹出元素，若列表为空则阻塞等待60秒</li>
      <li>返回值：value1</li>
      <li>说明：若列表key1为空，BLPOP命令会阻塞等待60秒，若60秒内有元素插入到列表key1，BLPOP命令会立即返回该元素。</li>
      <li>若60秒内没有元素插入到列表key1，BLPOP命令会返回NULL。</li>
    </ul>
  </li>
  <li>
    <p>BRPOP 命令：用于从列表的右端弹出元素，<strong>若列表为空则阻塞等待</strong>。</p>
  </li>
</ol>

<hr />

<ol>
  <li>如何利用List结构实现队列
    <ul>
      <li>入口和出口在不同的侧</li>
    </ul>
  </li>
  <li>如何利用List结构实现栈
    <ul>
      <li>入口和出口都在列表的同侧</li>
    </ul>
  </li>
  <li>如何利用List结构实现阻塞队列
    <ul>
      <li>入口和出口在不同的侧</li>
      <li>出队是BLPOP命令，入队是LPUSH命令，通过等待BLPOP命令的超时时间，实现阻塞队列。</li>
    </ul>
  </li>
</ol>

<h3 id="26-set-类型value-为集合命令">2.6 Set 类型（Value 为集合）命令</h3>

<ul>
  <li>Set 类型，Value是一个无序集合，类似Java中的HashSet,可视为哈希表的特殊实现。</li>
  <li>特点是：无序、元素不可重复、插入和删除快、查询效率高。</li>
</ul>

<ol>
  <li>
    <p>SADD 命令：用于将元素插入到集合中。</p>

    <ul>
      <li>语法：SADD [key] [value]</li>
      <li>示例：SADD key1 value1 # 将value1插入到集合key1中</li>
      <li>返回值：OK表示插入成功</li>
    </ul>
  </li>
  <li>
    <p>SREM 命令：用于从集合中删除元素。</p>

    <ul>
      <li>语法：SREM [key] [value]</li>
      <li>示例：SREM key1 value1 # 从集合key1中删除元素value1</li>
      <li>返回值：OK表示删除成功</li>
      <li>说明：若集合key1中不存在元素value1，SREM命令会返回OK。</li>
    </ul>
  </li>
  <li>
    <p>SCARD 命令：用于获取集合的元素数量。</p>

    <ul>
      <li>语法：SCARD [key]</li>
      <li>示例：SCARD key1 # 获取集合key1的元素数量</li>
    </ul>
  </li>
  <li>
    <p>SISMEMBER 命令：用于判断元素是否在集合中。</p>

    <ul>
      <li>语法：SISMEMBER [key] [value]</li>
      <li>示例：SISMEMBER key1 value1 # 判断集合key1中是否存在元素value1</li>
      <li>返回值：1表示存在，0表示不存在</li>
      <li>说明：若集合key1中不存在元素value1，SISMEMBER命令会返回0。</li>
      <li>若集合key1中存在元素value1，SISMEMBER命令会返回1。</li>
    </ul>
  </li>
  <li>
    <p>SMEMBERS 命令：用于获取集合中的所有元素。</p>

    <ul>
      <li>语法：SMEMBERS [key]</li>
      <li>示例：SMEMBERS key1 # 获取集合key1中的所有元素</li>
      <li>返回值：value1 value2 …</li>
    </ul>
  </li>
  <li>
    <p>SINTER 命令：用于获取多个集合的交集。</p>

    <ul>
      <li>语法：SINTER [key1] [key2] …</li>
      <li>示例：SINTER key1 key2 # 获取集合key1和集合key2的交集</li>
      <li>返回值：value1 value2 …</li>
    </ul>
  </li>
  <li>
    <p>SUNION 命令：用于获取多个集合的并集。</p>

    <ul>
      <li>语法：SUNION [key1] [key2] …</li>
      <li>示例：SUNION key1 key2 # 获取集合key1和集合key2的并集</li>
      <li>返回值：value1 value2 …</li>
    </ul>
  </li>
  <li>
    <p>SDIFF 命令：用于获取多个集合的差集。</p>

    <ul>
      <li>语法：SDIFF [key1] [key2] …</li>
      <li>示例：SDIFF key1 key2 # 获取集合key1和集合key2的差集</li>
      <li>返回值：value1 value2 …</li>
    </ul>
  </li>
</ol>

<h3 id="27-sortedset-类型value-为有序集合命令">2.7 SortedSet 类型（Value 为有序集合）命令</h3>

<ul>
  <li>SortedSet 类型，Value是一个有序集合，类似Java中的TreeSet功能，但是redis中底层是一个跳表（SkipList）加hash表。通过每个元素带一个score属性，对元素根据score排序。</li>
  <li>特点是：可排序，查询效率高。</li>
</ul>

<ol>
  <li>
    <p>ZADD 命令：用于将元素插入到有序集合中。</p>

    <ul>
      <li>语法：ZADD [key] [score] [value]</li>
      <li>示例：ZADD key1 100 value1 # 将value1插入到有序集合key1中，score为100</li>
      <li>返回值：OK表示插入成功</li>
    </ul>
  </li>
  <li>
    <p>ZREM 命令：用于从有序集合中删除元素。</p>

    <ul>
      <li>语法：ZREM [key] [value]</li>
      <li>示例：ZREM key1 value1 # 从有序集合key1中删除元素value1</li>
      <li>返回值：OK表示删除成功</li>
      <li>说明：若有序集合key1中不存在元素value1，ZREM命令会返回OK。</li>
    </ul>
  </li>
  <li>
    <p>ZCARD 命令：用于获取有序集合的元素数量。</p>

    <ul>
      <li>语法：ZCARD [key]</li>
      <li>示例：ZCARD key1 # 获取有序集合key1的元素数量</li>
      <li>返回值：2</li>
      <li>说明：有序集合key1中存在2个元素。</li>
    </ul>
  </li>
  <li>
    <p>ZSCORE 命令：用于获取有序集合中元素的score值。</p>

    <ul>
      <li>语法：ZSCORE [key] [value]</li>
      <li>示例：ZSCORE key1 value1 # 获取有序集合key1中元素value1的score值</li>
      <li>返回值：100</li>
      <li>说明：有序集合key1中元素value1的score值为100。</li>
      <li>若有序集合key1中不存在元素value1，ZSCORE命令会返回NULL。</li>
    </ul>
  </li>
  <li>
    <p>ZRANK 命令：用于获取有序集合中元素的排名。</p>

    <ul>
      <li>语法：ZRANK [key] [value]</li>
      <li>示例：ZRANK key1 value1 # 获取有序集合key1中元素value1的排名</li>
      <li>返回值：0</li>
      <li>说明：有序集合key1中元素value1的排名为0，从0开始计数。</li>
      <li>若有序集合key1中不存在元素value1，ZRANK命令会返回-1。</li>
    </ul>
  </li>
  <li>
    <p>ZCOUNT 命令：用于获取有序集合中范围元素的数量。</p>

    <ul>
      <li>语法：ZCOUNT [key] [min] [max]</li>
      <li>示例：ZCOUNT key1 0 100 # 获取有序集合key1中score在0到100之间的元素数量</li>
      <li>返回值：2</li>
      <li>说明：有序集合key1中score在0到100之间的元素数量为2。</li>
    </ul>
  </li>
  <li>
    <p>ZINCRBY 命令：用于对有序集合中元素的score值进行增量操作。</p>

    <ul>
      <li>语法：ZINCRBY [key] [increment] [value]</li>
      <li>示例：ZINCRBY key1 10 value1 # 对有序集合key1中元素value1的score值进行10的增量操作</li>
      <li>返回值：110</li>
      <li>说明：有序集合key1中元素value1的score值为110。</li>
    </ul>
  </li>
</ol>

<hr />

<blockquote>
  <p>默认排序是按score值升序排序。</p>
</blockquote>

<h2 id="3-redis-的java客户端">3. Redis 的JAVA客户端</h2>

<ol>
  <li>Jedis 是一个基于Java的Redis客户端，用于连接Redis服务器并执行Redis命令。 线程不安全的</li>
  <li>Lettuce 是一个基于Java的Redis客户端，用于连接Redis服务器并执行Redis命令。 线程安全的。支持Redis的哨兵模式、集群模式、管道模式</li>
  <li>Redisson 是一个基于Java的Redis客户端，用于连接Redis服务器并执行Redis命令。</li>
</ol>

<blockquote>
  <p>Spring Data Redis 是一个基于Java的Redis客户端，用于连接Redis服务器并执行Redis命令。</p>
</blockquote>

<h3 id="31-jedis-客户端">3.1 Jedis 客户端</h3>

<ol>
  <li>引入Maven依赖</li>
</ol>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>   <span class="nt">&lt;dependency&gt;</span>
      <span class="nt">&lt;groupId&gt;</span>redis.clients<span class="nt">&lt;/groupId&gt;</span>
      <span class="nt">&lt;artifactId&gt;</span>jedis<span class="nt">&lt;/artifactId&gt;</span>
      <span class="nt">&lt;version&gt;</span>3.7.0<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>

<ol>
  <li>连接Redis服务器</li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kn">package</span> <span class="nn">COM.HONG.test</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.AfterEach</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.BeforeEach</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.Test</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">redis.clients.jedis.Jedis</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">JedisConnectTest</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="nc">Jedis</span> <span class="n">jedis</span><span class="o">;</span>

    <span class="nd">@BeforeEach</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setUp</span><span class="o">()</span> <span class="o">{</span>
        <span class="n">jedis</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Jedis</span><span class="o">(</span><span class="s">"127.0.0.1"</span><span class="o">,</span> <span class="mi">6379</span><span class="o">);</span>
        <span class="n">jedis</span><span class="o">.</span><span class="na">select</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
        <span class="c1">// 测试是否连接上Redis</span>
        <span class="nc">String</span> <span class="n">result</span> <span class="o">=</span> <span class="n">jedis</span><span class="o">.</span><span class="na">ping</span><span class="o">();</span>

        <span class="k">if</span> <span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"PONG"</span><span class="o">))</span> <span class="o">{</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Redis连接成功"</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Redis连接失败"</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="nd">@Test</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">testConnect</span><span class="o">()</span> <span class="o">{</span>
        <span class="c1">// 设置数据</span>
        <span class="n">jedis</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="s">"Years"</span><span class="o">,</span> <span class="s">"2023"</span><span class="o">);</span>
        <span class="c1">// 获取数据</span>
        <span class="nc">String</span> <span class="n">value</span> <span class="o">=</span> <span class="n">jedis</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"Years"</span><span class="o">);</span>
        <span class="c1">// 打印获取到的值</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"获取到的值: "</span> <span class="o">+</span> <span class="n">value</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="nd">@AfterEach</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">tearDown</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">jedis</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">jedis</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
        <span class="o">}</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Redis连接已关闭"</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<ol>
  <li>Jedis 连接池</li>
</ol>

<ul>
  <li>连接池可以提高Redis客户端的性能，避免频繁创建和销毁连接。</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kn">package</span> <span class="nn">COM.HONG.test</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.AfterEach</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.Test</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">redis.clients.jedis.Jedis</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">redis.clients.jedis.JedisPool</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">redis.clients.jedis.JedisPoolConfig</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">JedisPoolConnectTest</span> <span class="o">{</span>
    <span class="c1">// 连接池</span>
    <span class="kd">private</span> <span class="kd">static</span> <span class="nc">JedisPool</span> <span class="n">pool</span><span class="o">;</span>
    <span class="c1">// 连接池配置</span>
    <span class="kd">static</span> <span class="o">{</span>
        <span class="nc">JedisPoolConfig</span> <span class="n">poolConfig</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">JedisPoolConfig</span><span class="o">();</span>
        <span class="n">poolConfig</span><span class="o">.</span><span class="na">setMaxTotal</span><span class="o">(</span><span class="mi">10</span><span class="o">);</span> <span class="c1">// 最大连接数</span>
        <span class="n">poolConfig</span><span class="o">.</span><span class="na">setMaxIdle</span><span class="o">(</span><span class="mi">5</span><span class="o">);</span> <span class="c1">// 最大空闲连接数</span>

        <span class="n">poolConfig</span><span class="o">.</span><span class="na">setMinIdle</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> <span class="c1">// 最小空闲连接数</span>

        <span class="n">pool</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">JedisPool</span><span class="o">(</span><span class="n">poolConfig</span><span class="o">,</span> <span class="s">"127.0.0.1"</span><span class="o">,</span> <span class="mi">6379</span><span class="o">);</span>
        <span class="nc">Jedis</span> <span class="n">jedis</span> <span class="o">=</span> <span class="n">pool</span><span class="o">.</span><span class="na">getResource</span><span class="o">();</span>

    <span class="o">}</span>

    <span class="c1">// 测试连接池</span>
    <span class="nd">@Test</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">testConnect</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">Jedis</span> <span class="n">jedis</span> <span class="o">=</span> <span class="n">pool</span><span class="o">.</span><span class="na">getResource</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">jedis</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">jedis</span><span class="o">.</span><span class="na">ping</span><span class="o">());</span>
        <span class="o">}</span>
        <span class="nc">String</span> <span class="n">value</span> <span class="o">=</span> <span class="n">jedis</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"Years"</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"获取到的值: "</span> <span class="o">+</span> <span class="n">value</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="nd">@AfterEach</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">tearDown</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">pool</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">pool</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
        <span class="o">}</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Redis连接池已关闭"</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>

</code></pre></div></div>

<ol>
  <li>Spring Data Redis</li>
</ol>

<ul>
  <li>Spring Data Redis 是一个基于Java的Redis客户端，用于连接Redis服务器并执行Redis命令。它提供了一个高层次的抽象，使得开发者可以更方便地使用Redis。</li>
</ul>

<p><img src="/assets/images/posts/redis-heima-course/1779526576313.webp" alt="" /></p>

<ul>
  <li>同样使用Spring-Boot工程，引入Spring Data Redis依赖。</li>
</ul>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nt">&lt;dependency&gt;</span>
        <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
        <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-data-redis<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>

<ul>
  <li>配置Redis连接</li>
</ul>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1"># 配置Redis连接</span>
<span class="na">spring</span><span class="pi">:</span>
  <span class="na">data</span><span class="pi">:</span>
    <span class="na">redis</span><span class="pi">:</span>
      <span class="na">host</span><span class="pi">:</span> <span class="s">127.0.0.1</span>
      <span class="na">port</span><span class="pi">:</span> <span class="m">6379</span>
<span class="c1">#      password:</span>
      <span class="na">timeout</span><span class="pi">:</span> <span class="s">60s</span>
      <span class="na">lettuce</span><span class="pi">:</span>
        <span class="na">pool</span><span class="pi">:</span>
          <span class="na">max-active</span><span class="pi">:</span> <span class="m">8</span>
          <span class="na">max-idle</span><span class="pi">:</span> <span class="m">8</span>
          <span class="na">min-idle</span><span class="pi">:</span> <span class="m">0</span>
</code></pre></div></div>

<ul>
  <li>测试代码</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.hong.redisproj2</span><span class="o">;</span>


<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.Test</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.beans.factory.annotation.Autowired</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.boot.test.context.SpringBootTest</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.connection.RedisConnectionFactory</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.RedisTemplate</span><span class="o">;</span>

<span class="nd">@SpringBootTest</span>
<span class="kd">class</span> <span class="nc">Redisproj2ApplicationTests</span> <span class="o">{</span>

    <span class="c1">//注入RedisTemplate</span>
    <span class="nd">@Autowired</span>
    <span class="kd">private</span> <span class="nc">RedisTemplate</span> <span class="n">redisTemplate</span><span class="o">;</span> <span class="c1">// 泛型表示支持字符串键和对象值</span>


    <span class="nd">@Test</span>
    <span class="kt">void</span> <span class="nf">contextLoads</span><span class="o">()</span> <span class="o">{</span>
        <span class="c1">// 测试连接是否成功</span>
        <span class="n">redisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="s">"Redis1"</span><span class="o">,</span> <span class="s">"Hello Redis!"</span><span class="o">);</span>
        <span class="nc">String</span> <span class="n">value</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">redisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="s">"Redis1"</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Redis1: "</span> <span class="o">+</span> <span class="n">value</span><span class="o">);</span>
    <span class="o">}</span>

<span class="o">}</span>

</code></pre></div></div>

<hr />

<blockquote>
  <p>再使用连接池进行写入Redis数据时，实际上传入参数是Object类型。它内部会自动将Object类型转换为Redis支持的类型。但是内部会进行序列化操作，将Object类型转换为字节数组。</p>

  <p>想要通过客户端访问时，实际上键值对要进行反序列化操作，将字节数组转换为Object类型。</p>

  <p>类似于如此：<strong>“Redis1: Hello Redis!”</strong> 我们通过上示例进行写入，但是通过redis-cli查看时，发现键值对<strong>是“Redis1: \xac\xed\x00\x05t\x00\x0cHello Redis!”。</strong></p>
</blockquote>

<h4 id="311-序列化设置">3.1.1 序列化设置</h4>

<ol>
  <li>序列化器</li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RedisConfig</span> <span class="o">{</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">RedisTemplate</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="nf">redisTemplate</span><span class="o">(</span><span class="nc">RedisConnectionFactory</span> <span class="n">connectionFactory</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">RedisTemplate</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">template</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RedisTemplate</span><span class="o">&lt;&gt;();</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setConnectionFactory</span><span class="o">(</span><span class="n">connectionFactory</span><span class="o">);</span> <span class="c1">// 设置连接工厂</span>
        <span class="c1">// 设置序列化工具，JSON序列化</span>
        <span class="nc">GenericJackson2JsonRedisSerializer</span> <span class="n">serializer</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GenericJackson2JsonRedisSerializer</span><span class="o">();</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setKeySerializer</span><span class="o">(</span><span class="k">new</span> <span class="nc">StringRedisSerializer</span><span class="o">());</span> <span class="c1">// 设置键序列化器</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setValueSerializer</span><span class="o">(</span><span class="n">serializer</span><span class="o">);</span> <span class="c1">// 设置值序列化器</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setHashKeySerializer</span><span class="o">(</span><span class="k">new</span> <span class="nc">StringRedisSerializer</span><span class="o">());</span> <span class="c1">// 设置哈希键序列化器</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setHashValueSerializer</span><span class="o">(</span><span class="n">serializer</span><span class="o">);</span> <span class="c1">// 设置哈希值序列化器</span>
        <span class="n">template</span><span class="o">.</span><span class="na">afterPropertiesSet</span><span class="o">();</span>
        <span class="k">return</span> <span class="n">template</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li>这种方式序列化可以将Object类型转换为JSON字符串，然后存储到Redis中。</li>
  <li>反序列化时，会将JSON字符串转换为Object类型。</li>
  <li>结果类似于此：</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>get user:1
<span class="s2">"{</span><span class="se">\"</span><span class="s2">@class</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"</span><span class="s2">com.hong.redisproj2.User</span><span class="se">\"</span><span class="s2">,</span><span class="se">\"</span><span class="s2">id</span><span class="se">\"</span><span class="s2">:1,</span><span class="se">\"</span><span class="s2">name</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"\x</span><span class="s2">e5</span><span class="se">\x</span><span class="s2">bc</span><span class="se">\x</span><span class="s2">a0</span><span class="se">\x</span><span class="s2">e4</span><span class="se">\x</span><span class="s2">b8</span><span class="se">\x</span><span class="s2">89</span><span class="se">\"</span><span class="s2">,</span><span class="se">\"</span><span class="s2">email</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"</span><span class="s2">zhangsan@example.com</span><span class="se">\"</span><span class="s2">}"</span>

</code></pre></div></div>

<ol>
  <li>使用StringRedisSerializer 和 Objectmapper对对象进行JSON序列化 进行序列化设置</li>
</ol>

<ul>
  <li>测试代码</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.hong.redisproj2</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">com.fasterxml.jackson.core.JsonProcessingException</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.fasterxml.jackson.databind.ObjectMapper</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.Test</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.beans.factory.annotation.Autowired</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.boot.test.context.SpringBootTest</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.StringRedisTemplate</span><span class="o">;</span>

<span class="nd">@SpringBootTest</span>
<span class="kd">class</span> <span class="nc">StringRedisTemplateTest</span> <span class="o">{</span>

    <span class="nd">@Autowired</span>
    <span class="kd">private</span> <span class="nc">StringRedisTemplate</span> <span class="n">stringRedisTemplate</span><span class="o">;</span>

    <span class="nd">@Test</span>
    <span class="kt">void</span> <span class="nf">testStringOperations</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">String</span> <span class="n">key</span> <span class="o">=</span> <span class="s">"test:string"</span><span class="o">;</span>
        <span class="nc">String</span> <span class="n">value</span> <span class="o">=</span> <span class="s">"Hello StringRedisTemplate!"</span><span class="o">;</span>

        <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>
        <span class="nc">String</span> <span class="n">retrievedValue</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>

        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Key: "</span> <span class="o">+</span> <span class="n">key</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Value: "</span> <span class="o">+</span> <span class="n">retrievedValue</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Test passed: "</span> <span class="o">+</span> <span class="n">value</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">retrievedValue</span><span class="o">));</span>
    <span class="o">}</span>

    <span class="nd">@Test</span>
    <span class="kt">void</span> <span class="nf">testObjectSerializationWithObjectMapper</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">ObjectMapper</span> <span class="n">objectMapper</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjectMapper</span><span class="o">();</span>
        <span class="nc">User</span> <span class="n">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">User</span><span class="o">(</span><span class="mi">1L</span><span class="o">,</span> <span class="s">"张三"</span><span class="o">,</span> <span class="s">"zhangsan@example.com"</span><span class="o">,</span> <span class="mi">25</span><span class="o">);</span>

        <span class="k">try</span> <span class="o">{</span>
            <span class="nc">String</span> <span class="n">key</span> <span class="o">=</span> <span class="s">"test:user:"</span> <span class="o">+</span> <span class="n">user</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>

            <span class="nc">String</span> <span class="n">jsonValue</span> <span class="o">=</span> <span class="n">objectMapper</span><span class="o">.</span><span class="na">writeValueAsString</span><span class="o">(</span><span class="n">user</span><span class="o">);</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Serialized JSON: "</span> <span class="o">+</span> <span class="n">jsonValue</span><span class="o">);</span>

            <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">jsonValue</span><span class="o">);</span>

            <span class="nc">String</span> <span class="n">retrievedJson</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Retrieved JSON: "</span> <span class="o">+</span> <span class="n">retrievedJson</span><span class="o">);</span>

            <span class="nc">User</span> <span class="n">retrievedUser</span> <span class="o">=</span> <span class="n">objectMapper</span><span class="o">.</span><span class="na">readValue</span><span class="o">(</span><span class="n">retrievedJson</span><span class="o">,</span> <span class="nc">User</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Retrieved User: "</span> <span class="o">+</span> <span class="n">retrievedUser</span><span class="o">);</span>

            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Test passed: "</span> <span class="o">+</span> <span class="n">user</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">retrievedUser</span><span class="o">));</span>

        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">JsonProcessingException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">err</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"JSON processing error: "</span> <span class="o">+</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
            <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="nd">@Test</span>
    <span class="kt">void</span> <span class="nf">testMultipleUsers</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">ObjectMapper</span> <span class="n">objectMapper</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjectMapper</span><span class="o">();</span>

        <span class="nc">User</span> <span class="n">user1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">User</span><span class="o">(</span><span class="mi">1L</span><span class="o">,</span> <span class="s">"张三"</span><span class="o">,</span> <span class="s">"zhangsan@example.com"</span><span class="o">,</span> <span class="mi">25</span><span class="o">);</span>
        <span class="nc">User</span> <span class="n">user2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">User</span><span class="o">(</span><span class="mi">2L</span><span class="o">,</span> <span class="s">"李四"</span><span class="o">,</span> <span class="s">"lisi@example.com"</span><span class="o">,</span> <span class="mi">30</span><span class="o">);</span>
        <span class="nc">User</span> <span class="n">user3</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">User</span><span class="o">(</span><span class="mi">3L</span><span class="o">,</span> <span class="s">"王五"</span><span class="o">,</span> <span class="s">"wangwu@example.com"</span><span class="o">,</span> <span class="mi">28</span><span class="o">);</span>

        <span class="k">try</span> <span class="o">{</span>
            <span class="nc">String</span> <span class="n">key1</span> <span class="o">=</span> <span class="s">"test:user:"</span> <span class="o">+</span> <span class="n">user1</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
            <span class="nc">String</span> <span class="n">key2</span> <span class="o">=</span> <span class="s">"test:user:"</span> <span class="o">+</span> <span class="n">user2</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
            <span class="nc">String</span> <span class="n">key3</span> <span class="o">=</span> <span class="s">"test:user:"</span> <span class="o">+</span> <span class="n">user3</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>

            <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="n">key1</span><span class="o">,</span> <span class="n">objectMapper</span><span class="o">.</span><span class="na">writeValueAsString</span><span class="o">(</span><span class="n">user1</span><span class="o">));</span>
            <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="n">key2</span><span class="o">,</span> <span class="n">objectMapper</span><span class="o">.</span><span class="na">writeValueAsString</span><span class="o">(</span><span class="n">user2</span><span class="o">));</span>
            <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="n">key3</span><span class="o">,</span> <span class="n">objectMapper</span><span class="o">.</span><span class="na">writeValueAsString</span><span class="o">(</span><span class="n">user3</span><span class="o">));</span>

            <span class="nc">User</span> <span class="n">retrievedUser1</span> <span class="o">=</span> <span class="n">objectMapper</span><span class="o">.</span><span class="na">readValue</span><span class="o">(</span><span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">key1</span><span class="o">),</span> <span class="nc">User</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
            <span class="nc">User</span> <span class="n">retrievedUser2</span> <span class="o">=</span> <span class="n">objectMapper</span><span class="o">.</span><span class="na">readValue</span><span class="o">(</span><span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">key2</span><span class="o">),</span> <span class="nc">User</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
            <span class="nc">User</span> <span class="n">retrievedUser3</span> <span class="o">=</span> <span class="n">objectMapper</span><span class="o">.</span><span class="na">readValue</span><span class="o">(</span><span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">key3</span><span class="o">),</span> <span class="nc">User</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>

            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Retrieved User 1: "</span> <span class="o">+</span> <span class="n">retrievedUser1</span><span class="o">);</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Retrieved User 2: "</span> <span class="o">+</span> <span class="n">retrievedUser2</span><span class="o">);</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Retrieved User 3: "</span> <span class="o">+</span> <span class="n">retrievedUser3</span><span class="o">);</span>

            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"All users stored and retrieved successfully!"</span><span class="o">);</span>

        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">JsonProcessingException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">err</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"JSON processing error: "</span> <span class="o">+</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
            <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="nd">@Test</span>
    <span class="kt">void</span> <span class="nf">testHashOperations</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">ObjectMapper</span> <span class="n">objectMapper</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjectMapper</span><span class="o">();</span>
        <span class="nc">User</span> <span class="n">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">User</span><span class="o">(</span><span class="mi">1L</span><span class="o">,</span> <span class="s">"赵六"</span><span class="o">,</span> <span class="s">"zhaoliu@example.com"</span><span class="o">,</span> <span class="mi">35</span><span class="o">);</span>

        <span class="k">try</span> <span class="o">{</span>
            <span class="nc">String</span> <span class="n">hashKey</span> <span class="o">=</span> <span class="s">"test:user:hash:"</span> <span class="o">+</span> <span class="n">user</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>

            <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForHash</span><span class="o">().</span><span class="na">put</span><span class="o">(</span><span class="n">hashKey</span><span class="o">,</span> <span class="s">"id"</span><span class="o">,</span> <span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getId</span><span class="o">()));</span>
            <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForHash</span><span class="o">().</span><span class="na">put</span><span class="o">(</span><span class="n">hashKey</span><span class="o">,</span> <span class="s">"name"</span><span class="o">,</span> <span class="n">user</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
            <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForHash</span><span class="o">().</span><span class="na">put</span><span class="o">(</span><span class="n">hashKey</span><span class="o">,</span> <span class="s">"email"</span><span class="o">,</span> <span class="n">user</span><span class="o">.</span><span class="na">getEmail</span><span class="o">());</span>
            <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForHash</span><span class="o">().</span><span class="na">put</span><span class="o">(</span><span class="n">hashKey</span><span class="o">,</span> <span class="s">"age"</span><span class="o">,</span> <span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getAge</span><span class="o">()));</span>

            <span class="nc">String</span> <span class="n">retrievedId</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForHash</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">hashKey</span><span class="o">,</span> <span class="s">"id"</span><span class="o">);</span>
            <span class="nc">String</span> <span class="n">retrievedName</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForHash</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">hashKey</span><span class="o">,</span> <span class="s">"name"</span><span class="o">);</span>
            <span class="nc">String</span> <span class="n">retrievedEmail</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForHash</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">hashKey</span><span class="o">,</span> <span class="s">"email"</span><span class="o">);</span>
            <span class="nc">String</span> <span class="n">retrievedAge</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForHash</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">hashKey</span><span class="o">,</span> <span class="s">"age"</span><span class="o">);</span>

            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Hash operations test:"</span><span class="o">);</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"ID: "</span> <span class="o">+</span> <span class="n">retrievedId</span><span class="o">);</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Name: "</span> <span class="o">+</span> <span class="n">retrievedName</span><span class="o">);</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Email: "</span> <span class="o">+</span> <span class="n">retrievedEmail</span><span class="o">);</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Age: "</span> <span class="o">+</span> <span class="n">retrievedAge</span><span class="o">);</span>

            <span class="nc">User</span> <span class="n">reconstructedUser</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">User</span><span class="o">(</span>
                    <span class="nc">Long</span><span class="o">.</span><span class="na">parseLong</span><span class="o">(</span><span class="n">retrievedId</span><span class="o">),</span>
                    <span class="n">retrievedName</span><span class="o">,</span>
                    <span class="n">retrievedEmail</span><span class="o">,</span>
                    <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">retrievedAge</span><span class="o">)</span>
            <span class="o">);</span>

            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Reconstructed User: "</span> <span class="o">+</span> <span class="n">reconstructedUser</span><span class="o">);</span>

        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">err</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Hash operations error: "</span> <span class="o">+</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
            <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>

</code></pre></div></div>

<ul>
  <li>测试类对象</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.hong.redisproj2</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">User</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="nc">Long</span> <span class="n">id</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">email</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">Integer</span> <span class="n">age</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">User</span><span class="o">()</span> <span class="o">{</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nf">User</span><span class="o">(</span><span class="nc">Long</span> <span class="n">id</span><span class="o">,</span> <span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">String</span> <span class="n">email</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">age</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="n">id</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">email</span> <span class="o">=</span> <span class="n">email</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">age</span> <span class="o">=</span> <span class="n">age</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">Long</span> <span class="nf">getId</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">id</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setId</span><span class="o">(</span><span class="nc">Long</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="n">id</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">name</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">getEmail</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">email</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setEmail</span><span class="o">(</span><span class="nc">String</span> <span class="n">email</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">email</span> <span class="o">=</span> <span class="n">email</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getAge</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">age</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setAge</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">age</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">age</span> <span class="o">=</span> <span class="n">age</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="s">"User{"</span> <span class="o">+</span>
                <span class="s">"id="</span> <span class="o">+</span> <span class="n">id</span> <span class="o">+</span>
                <span class="s">", name='"</span> <span class="o">+</span> <span class="n">name</span> <span class="o">+</span> <span class="sc">'\''</span> <span class="o">+</span>
                <span class="s">", email='"</span> <span class="o">+</span> <span class="n">email</span> <span class="o">+</span> <span class="sc">'\''</span> <span class="o">+</span>
                <span class="s">", age="</span> <span class="o">+</span> <span class="n">age</span> <span class="o">+</span>
                <span class="sc">'}'</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>


<span class="o">-</span> <span class="n">序列化对象注册</span>
<span class="err">```</span><span class="n">java</span>
<span class="kn">package</span> <span class="nn">com.hong.redisproj2</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">org.springframework.context.annotation.Bean</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.context.annotation.Configuration</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.connection.RedisConnectionFactory</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.RedisTemplate</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.StringRedisTemplate</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.serializer.StringRedisSerializer</span><span class="o">;</span>

<span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RedisConfig</span> <span class="o">{</span>

    <span class="nd">@Bean</span>  <span class="c1">// 使用StringRedisSerializer对键进行序列化和反序列化</span>
    <span class="kd">public</span> <span class="nc">RedisTemplate</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="nf">redisTemplate</span><span class="o">(</span><span class="nc">RedisConnectionFactory</span> <span class="n">connectionFactory</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">RedisTemplate</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">template</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RedisTemplate</span><span class="o">&lt;&gt;();</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setConnectionFactory</span><span class="o">(</span><span class="n">connectionFactory</span><span class="o">);</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setKeySerializer</span><span class="o">(</span><span class="k">new</span> <span class="nc">StringRedisSerializer</span><span class="o">());</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setHashKeySerializer</span><span class="o">(</span><span class="k">new</span> <span class="nc">StringRedisSerializer</span><span class="o">());</span>
        <span class="n">template</span><span class="o">.</span><span class="na">afterPropertiesSet</span><span class="o">();</span>
        <span class="k">return</span> <span class="n">template</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">StringRedisTemplate</span> <span class="nf">stringRedisTemplate</span><span class="o">(</span><span class="nc">RedisConnectionFactory</span> <span class="n">connectionFactory</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">StringRedisTemplate</span> <span class="n">template</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringRedisTemplate</span><span class="o">();</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setConnectionFactory</span><span class="o">(</span><span class="n">connectionFactory</span><span class="o">);</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setKeySerializer</span><span class="o">(</span><span class="k">new</span> <span class="nc">StringRedisSerializer</span><span class="o">());</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setHashKeySerializer</span><span class="o">(</span><span class="k">new</span> <span class="nc">StringRedisSerializer</span><span class="o">());</span>
        <span class="n">template</span><span class="o">.</span><span class="na">afterPropertiesSet</span><span class="o">();</span>
        <span class="k">return</span> <span class="n">template</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>


</code></pre></div></div>

<ul>
  <li>通过redis-cli查看时，键值对类似于此：</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>get <span class="s2">"test:user:2"</span>
<span class="s2">"{</span><span class="se">\"</span><span class="s2">id</span><span class="se">\"</span><span class="s2">:2,</span><span class="se">\"</span><span class="s2">name</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"\x</span><span class="s2">e6</span><span class="se">\x</span><span class="s2">9d</span><span class="se">\x</span><span class="s2">8e</span><span class="se">\x</span><span class="s2">e5</span><span class="se">\x</span><span class="s2">9b</span><span class="se">\x</span><span class="s2">9b</span><span class="se">\"</span><span class="s2">,</span><span class="se">\"</span><span class="s2">email</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"</span><span class="s2">lisi@example.com</span><span class="se">\"</span><span class="s2">,</span><span class="se">\"</span><span class="s2">age</span><span class="se">\"</span><span class="s2">:30}"</span>
</code></pre></div></div>

<h2 id="4-黑马点评部分">4. 黑马点评部分</h2>

<h3 id="41-session-登录">4.1 Session 登录</h3>

<p><img src="/assets/images/posts/redis-heima-course/1779763021308.webp" alt="" /></p>

<h3 id="42-拦截器在登录校验时线程不安全问题">4.2 拦截器在登录校验时线程不安全问题</h3>

<ul>
  <li>问题：
    <ul>
      <li>拦截器是单例的：Spring 容器中默认一个拦截器 Bean 只有一个对象。</li>
      <li>多个请求线程同时访问：每个请求都会进入同一个拦截器的 preHandle 方法。</li>
    </ul>
  </li>
  <li>解决：使用threadLocal存储登录信息，避免线程不安全问题。
    <ul>
      <li>拦截器中添加threadLocal，用于存储登录信息。</li>
      <li>拦截器中添加方法，用于获取登录信息。preHandleLocal.get() 在开始时获取登录信息，afterCompletionLocal.remove() 在结束时清除登录信息。</li>
    </ul>
  </li>
</ul>

<hr />

<blockquote>
  <p>// 拦截器并不归于容器管理，需要手动创建，因此它需要在 ApplicationRunner 中创建
// 同时，在使用其他如RedisTemplate等组件时，也需要在 内部或传入，无法进行自动注入</p>
</blockquote>

<hr />

<p><img src="/assets/images/posts/redis-heima-course/1779780053605.webp" alt="" /></p>

<blockquote>
  <p>Session 在针对多台Tomcat时，是相互独立的。每个Tomcat实例都有自己的Session管理器，不会共享Session。</p>

  <p>解决这种问题就是通过Redis来存储Session信息。</p>
</blockquote>

<h3 id="43-redis-进行登录校验管理">4.3 Redis 进行登录校验管理</h3>

<p><img src="/assets/images/posts/redis-heima-course/1779780538903.webp" alt="" /></p>

<ul>
  <li>JWT（JSON Web Token）方式，通过后端生成JWT，作为唯一的标识符（区分每个用户，同时标识Session）。
<img src="/assets/images/posts/redis-heima-course/1779780624783.webp" alt="" /></li>
</ul>

<p><img src="/assets/images/posts/redis-heima-course/1779785801069.webp" alt="" /></p>

<h3 id="44-redis-进行商户缓存">4.4 Redis 进行商户缓存</h3>

<h4 id="441-缓存介绍">4.4.1 缓存介绍</h4>

<ul>
  <li>缓存就是数据交换的中间层，用于临时存储数据，提高数据访问速度。</li>
</ul>

<ol>
  <li>
    <p>缓存的作用：</p>

    <ul>
      <li>提高数据访问速度：通过缓存热门数据，减少数据库访问次数。</li>
      <li>减少数据库压力：缓存可以缓存数据库查询结果，避免重复查询。</li>
      <li>提供实时数据：缓存可以提供实时数据，避免依赖数据库。</li>
    </ul>
  </li>
  <li>
    <p>缓存的成本：</p>

    <ul>
      <li>存储成本：缓存需要占用内存空间，增加服务器成本。</li>
      <li>计算成本：缓存需要占用CPU资源，增加服务器成本。</li>
      <li>数据一致性问题：缓存数据和数据库数据不一致，需要通过缓存失效策略来解决。</li>
    </ul>
  </li>
</ol>

<p><img src="/assets/images/posts/redis-heima-course/1779846759124.webp" alt="" /></p>

<h4 id="443-缓存更新策略">4.4.3 缓存更新策略</h4>

<ul>
  <li>JMeter 工具：用于测试缓存更新策略的性能。</li>
</ul>

<p><img src="/assets/images/posts/redis-heima-course/1779851152947.webp" alt="" /></p>

<p><img src="/assets/images/posts/redis-heima-course/1779851431910.webp" alt="" />
<img src="/assets/images/posts/redis-heima-course/1779851614179.webp" alt="" />
<img src="/assets/images/posts/redis-heima-course/1779852005519.webp" alt="" /></p>

<ol>
  <li>缓存穿透</li>
</ol>

<ul>
  <li>缓存穿透是指当前请求的key不存在缓存中，也不存在数据库中，导致数据库压力增加。
<img src="/assets/images/posts/redis-heima-course/1779862593042.webp" alt="" /></li>
  <li>解决方案：
    <ul>
      <li>缓存空对象，避免数据库压力增加。 这种方式会增加额外内存消耗，可能造成短期的不一致性。</li>
      <li>布隆过滤器，避免缓存穿透。</li>
      <li>并不仅限于此，也可以通过其他方式来避免缓存穿透。</li>
      <li>数据校验，用户权限校验。</li>
    </ul>
  </li>
</ul>

<hr />

<blockquote>
  <p>缓存穿透的一个解决代码示例：通过缓存空对象，避免数据库压力增加。</p>
</blockquote>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">Result</span> <span class="nf">queryById</span><span class="o">(</span><span class="nc">Long</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// 1. 从Redis缓存中查询</span>
        <span class="nc">String</span> <span class="n">key</span> <span class="o">=</span> <span class="no">CACHE_SHOP_KEY</span> <span class="o">+</span> <span class="n">id</span><span class="o">;</span>
        <span class="nc">String</span> <span class="n">shopJson</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>
        <span class="c1">// 2. 判断缓存中是否有数据 StrUtil.isNotBlank() 方法判断字符串是否为空，不包含空格等</span>
        <span class="k">if</span> <span class="o">(</span><span class="nc">StrUtil</span><span class="o">.</span><span class="na">isNotBlank</span><span class="o">(</span><span class="n">shopJson</span><span class="o">))</span> <span class="o">{</span>
            <span class="c1">// 存在数据，直接返回</span>
            <span class="nc">Shop</span> <span class="n">shop</span> <span class="o">=</span> <span class="nc">JSONUtil</span><span class="o">.</span><span class="na">toBean</span><span class="o">(</span><span class="n">shopJson</span><span class="o">,</span> <span class="nc">Shop</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">shop</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="c1">// 如果缓存中有数据，但是是空字符串，说明店铺不存在，直接返回失败</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">shopJson</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"店铺不存在"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="c1">// 4. 如果没有数据，从数据库中查询</span>
        <span class="nc">Shop</span> <span class="n">shop</span> <span class="o">=</span> <span class="n">getById</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>

        <span class="k">if</span> <span class="o">(</span><span class="n">shop</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span><span class="c1">// 店铺不存在</span>
            <span class="c1">// 创建空对象，避免缓存穿透问题</span>
            <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="s">""</span><span class="o">,</span><span class="no">CACHE_NULL_TTL</span><span class="o">,</span> <span class="nc">TimeUnit</span><span class="o">.</span><span class="na">MINUTES</span><span class="o">);</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"店铺不存在"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="c1">// 6. 如果数据库中也有数据，写入缓存，返回数据</span>
        <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="nc">JSONUtil</span><span class="o">.</span><span class="na">toJsonStr</span><span class="o">(</span><span class="n">shop</span><span class="o">),</span> <span class="no">CACHE_SHOP_TTL</span><span class="o">,</span> <span class="nc">TimeUnit</span><span class="o">.</span><span class="na">MINUTES</span><span class="o">);</span>
        <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">shop</span><span class="o">);</span>
    <span class="o">}</span>

</code></pre></div></div>

<ol>
  <li>缓存雪崩</li>
</ol>

<ul>
  <li>缓存雪崩是指在同一时间，缓存中大量的的数据都失效期，导致数据库压力增加。
<img src="/assets/images/posts/redis-heima-course/1779864118801.webp" alt="" /></li>
</ul>

<ol>
  <li>缓存击穿（热点Key问题）</li>
</ol>

<ul>
  <li>缓存击穿问题叫热点key问题。一个被高并发访问并且缓存重建业务复杂的key突然失效，导致数据库压力增加。</li>
</ul>

<p><img src="/assets/images/posts/redis-heima-course/1779864334406.webp" alt="" /></p>

<p><img src="/assets/images/posts/redis-heima-course/1779864627348.webp" alt="" /></p>

<p><img src="/assets/images/posts/redis-heima-course/1779864639783.webp" alt="" /></p>

<ul>
  <li>互斥锁在Redis中可以使用setnx实现，因为setnx命令在key不存在时，才会设置成功，其他情况都会返回失败。</li>
  <li>互斥锁的实现原理是：
    <ul>
      <li>当线程A请求获取互斥锁时，会使用setnx命令设置互斥锁。</li>
      <li>如果互斥锁不存在，会设置互斥锁，返回成功。</li>
      <li>如果互斥锁存在，会返回失败。</li>
    </ul>
  </li>
  <li></li>
</ul>

<p><img src="/assets/images/posts/redis-heima-course/1779865012520.webp" alt="" /></p>

<ul>
  <li>热点Key互斥锁解决代码示例：</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
    <span class="c1">// 使用互斥锁来解决热点Key不存在问题</span>
    <span class="kd">public</span> <span class="nc">Shop</span> <span class="nf">queryWithMutex</span><span class="o">(</span><span class="nc">Long</span> <span class="n">id</span><span class="o">){</span>
        <span class="c1">// 1. 从Redis缓存中查询</span>
        <span class="nc">String</span> <span class="n">key</span> <span class="o">=</span> <span class="no">CACHE_SHOP_KEY</span> <span class="o">+</span> <span class="n">id</span><span class="o">;</span>
        <span class="nc">String</span> <span class="n">shopJson</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>
        <span class="c1">// 2. 判断缓存中是否有数据</span>
        <span class="k">if</span> <span class="o">(</span><span class="nc">StrUtil</span><span class="o">.</span><span class="na">isNotBlank</span><span class="o">(</span><span class="n">shopJson</span><span class="o">))</span> <span class="o">{</span>
            <span class="c1">// 存在数据，直接返回</span>
            <span class="nc">Shop</span> <span class="n">shop</span> <span class="o">=</span> <span class="nc">JSONUtil</span><span class="o">.</span><span class="na">toBean</span><span class="o">(</span><span class="n">shopJson</span><span class="o">,</span> <span class="nc">Shop</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
            <span class="k">return</span> <span class="n">shop</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="c1">// 如果缓存中有数据，但是是空字符串，说明店铺不存在，直接返回失败</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">shopJson</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="nc">Shop</span> <span class="n">shop</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
        <span class="nc">String</span> <span class="n">lockKey</span> <span class="o">=</span> <span class="no">LOCK_SHOP_KEY</span> <span class="o">+</span> <span class="n">id</span><span class="o">;</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="c1">// 3. 尝试获取互斥锁</span>
            <span class="kt">boolean</span> <span class="n">isLock</span> <span class="o">=</span> <span class="n">tryLock</span><span class="o">(</span><span class="n">lockKey</span><span class="o">,</span> <span class="no">LOCK_SHOP_TTL</span><span class="o">,</span> <span class="nc">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">);</span>
            <span class="k">if</span> <span class="o">(!</span><span class="n">isLock</span><span class="o">)</span> <span class="o">{</span>
                <span class="c1">// 获取互斥锁失败，休眠一段时间后重试</span>
                <span class="nc">Thread</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="mi">50</span><span class="o">);</span>
                <span class="k">return</span> <span class="nf">queryWithMutex</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
            <span class="o">}</span>
            <span class="c1">// 4. 如果没有数据，从数据库中查询</span>
            <span class="n">shop</span> <span class="o">=</span> <span class="n">getById</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">shop</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span><span class="c1">// 店铺不存在</span>
                <span class="c1">// 创建空对象，避免缓存穿透问题</span>
                <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="s">""</span><span class="o">,</span><span class="no">CACHE_NULL_TTL</span><span class="o">,</span> <span class="nc">TimeUnit</span><span class="o">.</span><span class="na">MINUTES</span><span class="o">);</span>
                <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
            <span class="o">}</span>
            <span class="c1">// 6. 如果数据库中也有数据，写入缓存，返回数据</span>
            <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="nc">JSONUtil</span><span class="o">.</span><span class="na">toJsonStr</span><span class="o">(</span><span class="n">shop</span><span class="o">),</span> <span class="no">CACHE_SHOP_TTL</span><span class="o">,</span> <span class="nc">TimeUnit</span><span class="o">.</span><span class="na">MINUTES</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
        <span class="o">}</span><span class="k">finally</span> <span class="o">{</span>
            <span class="c1">// 7. 释放互斥锁</span>
            <span class="n">unlock</span><span class="o">(</span><span class="n">lockKey</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">return</span> <span class="n">shop</span><span class="o">;</span>
    <span class="o">}</span>


</code></pre></div></div>

<ul>
  <li>逻辑过期解决热点Key问题：
<img src="/assets/images/posts/redis-heima-course/1779867956048.webp" alt="" /></li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
   <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">StringRedisTemplate</span> <span class="n">stringRedisTemplate</span><span class="o">;</span>

    <span class="c1">// 逻辑过期缓存时，调用线程进行缓存重建 通过线程池来进行，避免频繁线程销毁申请问题</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ExecutorService</span> <span class="no">CACHE_REBUILD_EXECUTOR</span> <span class="o">=</span> <span class="nc">Executors</span><span class="o">.</span><span class="na">newFixedThreadPool</span><span class="o">(</span><span class="mi">10</span><span class="o">);</span>



    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">Result</span> <span class="nf">queryById</span><span class="o">(</span><span class="nc">Long</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Shop</span> <span class="n">shop</span> <span class="o">=</span> <span class="n">queryWithLogicExpire</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">shop</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"店铺不存在"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">shop</span><span class="o">);</span>
    <span class="o">}</span>


    <span class="c1">// 使用逻辑过期时间来缓解缓存击穿，热点key问题 (这个代码实际在访问时，有bug，如果缓存中没有事先加载缓存数据，启动时，会导致无法查询到数据情况)</span>
    <span class="kd">public</span> <span class="nc">Shop</span> <span class="nf">queryWithLogicExpire</span><span class="o">(</span><span class="nc">Long</span> <span class="n">id</span><span class="o">){</span>
        <span class="c1">// 1. 从Redis缓存中查询</span>
        <span class="nc">String</span> <span class="n">key</span> <span class="o">=</span> <span class="no">CACHE_SHOP_KEY</span> <span class="o">+</span> <span class="n">id</span><span class="o">;</span>
        <span class="nc">String</span> <span class="n">redisDataJson</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>
        <span class="c1">// 2. 判断缓存中是否有数据（逻辑过期需要启动前，将数据库数据先缓存到Redis缓存中才行）</span>
        <span class="k">if</span> <span class="o">(</span><span class="nc">StrUtil</span><span class="o">.</span><span class="na">isBlank</span><span class="o">(</span><span class="n">redisDataJson</span><span class="o">))</span> <span class="o">{</span>
            <span class="k">return</span> <span class="kc">null</span><span class="o">;</span><span class="c1">// 缓存中没有数据，直接返回失败</span>
        <span class="o">}</span>
        <span class="c1">// 3. 将RedisData转换为Shop</span>
        <span class="nc">RedisData</span> <span class="n">redisData</span> <span class="o">=</span> <span class="nc">JSONUtil</span><span class="o">.</span><span class="na">toBean</span><span class="o">(</span><span class="n">redisDataJson</span><span class="o">,</span> <span class="nc">RedisData</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
        <span class="nc">LocalDateTime</span> <span class="n">expireTime</span> <span class="o">=</span> <span class="n">redisData</span><span class="o">.</span><span class="na">getExpireTime</span><span class="o">();</span>
        <span class="nc">Shop</span> <span class="n">shop</span> <span class="o">=</span> <span class="nc">JSONUtil</span><span class="o">.</span><span class="na">toBean</span><span class="o">((</span><span class="nc">JSONObject</span><span class="o">)</span> <span class="n">redisData</span><span class="o">.</span><span class="na">getData</span><span class="o">(),</span> <span class="nc">Shop</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
        <span class="c1">// 4. 判断缓存是否过期 未过期，则直接返回结果即可</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">expireTime</span><span class="o">.</span><span class="na">isAfter</span><span class="o">(</span><span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">()))</span> <span class="o">{</span>
            <span class="k">return</span> <span class="n">shop</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="c1">// 5. 如果过期，需要缓存重建，使用互斥锁来进行重建</span>
        <span class="nc">String</span> <span class="n">lockKey</span> <span class="o">=</span> <span class="no">LOCK_SHOP_KEY</span> <span class="o">+</span> <span class="n">id</span><span class="o">;</span>
        <span class="kt">boolean</span> <span class="n">islock</span> <span class="o">=</span> <span class="n">tryLock</span><span class="o">(</span><span class="n">lockKey</span><span class="o">,</span> <span class="no">LOCK_SHOP_TTL</span><span class="o">,</span> <span class="nc">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">islock</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">// 如果获取成功，申请一个线程从线程池，用另一个线程进行更新缓存</span>
            <span class="no">CACHE_REBUILD_EXECUTOR</span><span class="o">.</span><span class="na">submit</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="o">{</span>
                <span class="k">try</span> <span class="o">{</span>
                    <span class="n">saveShop2Redis</span><span class="o">(</span><span class="n">id</span><span class="o">,</span> <span class="no">CACHE_SHOP_TTL</span><span class="o">);</span>
                <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">){</span>
                    <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
                <span class="o">}</span><span class="k">finally</span> <span class="o">{</span>
                    <span class="c1">// 8. 释放互斥锁</span>
                    <span class="n">unlock</span><span class="o">(</span><span class="n">lockKey</span><span class="o">);</span>
                <span class="o">}</span>
            <span class="o">});</span>
        <span class="o">}</span>
        <span class="c1">// 未获得锁的线程，直接使用旧缓存数据</span>
        <span class="k">return</span> <span class="n">shop</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="c1">// 将Shop+逻辑过期时间写入缓存</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">saveShop2Redis</span><span class="o">(</span><span class="nc">Long</span> <span class="n">id</span> <span class="o">,</span> <span class="nc">Long</span> <span class="n">expireTime</span><span class="o">){</span>
        <span class="c1">// 因为缓存中的数据，实际上最开始被添加都是从数据库中查询的，</span>
        <span class="c1">// 只要保证查询数据在添加时，保存缓存格式是RedisData，包含过期时间和数据就行</span>
        <span class="nc">Shop</span> <span class="n">shop</span> <span class="o">=</span> <span class="n">getById</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
        <span class="c1">// 封装RedisData</span>
        <span class="nc">RedisData</span> <span class="n">redisData</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RedisData</span><span class="o">();</span>
        <span class="n">redisData</span><span class="o">.</span><span class="na">setExpireTime</span><span class="o">(</span><span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">().</span><span class="na">plusSeconds</span><span class="o">(</span><span class="n">expireTime</span><span class="o">));</span>
        <span class="n">redisData</span><span class="o">.</span><span class="na">setData</span><span class="o">(</span><span class="n">shop</span><span class="o">);</span>
        <span class="c1">// 写入redis缓存 逻辑过期只在过期更新数据，在缓存中实际上一直存在</span>
        <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="no">CACHE_SHOP_KEY</span> <span class="o">+</span> <span class="n">id</span><span class="o">,</span>
                <span class="nc">JSONUtil</span><span class="o">.</span><span class="na">toJsonStr</span><span class="o">(</span><span class="n">redisData</span><span class="o">));</span>
    <span class="o">}</span>
</code></pre></div></div>

<h4 id="45-redis缓存工具封装">4.5 Redis缓存工具封装</h4>

<ol>
  <li>java 缓存工具类封装</li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kn">package</span> <span class="nn">com.hmdp.utils</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">cn.hutool.core.util.BooleanUtil</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">cn.hutool.core.util.StrUtil</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">cn.hutool.json.JSONObject</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">cn.hutool.json.JSONUtil</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.entity.Shop</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">lombok.extern.slf4j.Slf4j</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.StringRedisTemplate</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.stereotype.Component</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">javax.annotation.Resource</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.time.LocalDateTime</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.concurrent.ExecutorService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.concurrent.Executors</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.concurrent.TimeUnit</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.function.Function</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">static</span> <span class="n">com</span><span class="o">.</span><span class="na">hmdp</span><span class="o">.</span><span class="na">utils</span><span class="o">.</span><span class="na">RedisConstants</span><span class="o">.*;</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">com</span><span class="o">.</span><span class="na">hmdp</span><span class="o">.</span><span class="na">utils</span><span class="o">.</span><span class="na">RedisConstants</span><span class="o">.</span><span class="na">CACHE_SHOP_TTL</span><span class="o">;</span>


<span class="c1">// 缓存工具类的作用，就是包装，将对于数据库访问的形式都进行封装，方便调用</span>
<span class="c1">// 同时针对缓存穿透问题和热点key问题，进行不同的缓存机制。</span>
<span class="c1">// 好处是 上级代码在调用时，只需要调用缓存工具类的方法，不需要关注缓存的实现细节。</span>

<span class="nd">@Component</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CacheClient</span> <span class="o">{</span>


    <span class="kd">private</span>  <span class="kd">final</span> <span class="nc">StringRedisTemplate</span> <span class="n">stringRedisTemplate</span><span class="o">;</span>

    <span class="c1">// 逻辑过期缓存时，调用线程进行缓存重建 通过线程池来进行，避免频繁线程销毁申请问题</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ExecutorService</span> <span class="no">CACHE_REBUILD_EXECUTOR</span> <span class="o">=</span> <span class="nc">Executors</span><span class="o">.</span><span class="na">newFixedThreadPool</span><span class="o">(</span><span class="mi">10</span><span class="o">);</span>

    <span class="kd">public</span> <span class="nf">CacheClient</span><span class="o">(</span><span class="nc">StringRedisTemplate</span> <span class="n">stringRedisTemplate</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">stringRedisTemplate</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="c1">// 将对象存储进入缓存</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setCache</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">value</span><span class="o">,</span> <span class="kt">long</span> <span class="n">expireTime</span><span class="o">,</span> <span class="nc">TimeUnit</span> <span class="n">timeUnit</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="n">key</span><span class="o">,</span>
                <span class="nc">JSONUtil</span><span class="o">.</span><span class="na">toJsonStr</span><span class="o">(</span><span class="n">value</span><span class="o">),</span> <span class="n">expireTime</span><span class="o">,</span> <span class="n">timeUnit</span><span class="o">);</span>
    <span class="o">}</span>


    <span class="c1">// 将逻辑过期时间+对象数据(RedisData)存储进入缓存</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setCacheWithLogicExpire</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">value</span><span class="o">,</span> <span class="kt">long</span> <span class="n">expireTime</span><span class="o">,</span> <span class="nc">TimeUnit</span> <span class="n">timeUnit</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">RedisData</span> <span class="n">redisData</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RedisData</span><span class="o">();</span>
        <span class="n">redisData</span><span class="o">.</span><span class="na">setExpireTime</span><span class="o">(</span><span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">().</span><span class="na">plusSeconds</span><span class="o">(</span><span class="n">timeUnit</span><span class="o">.</span><span class="na">toSeconds</span><span class="o">(</span><span class="n">expireTime</span><span class="o">)));</span>
        <span class="n">redisData</span><span class="o">.</span><span class="na">setData</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
        <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="n">key</span><span class="o">,</span>
                <span class="nc">JSONUtil</span><span class="o">.</span><span class="na">toJsonStr</span><span class="o">(</span><span class="n">redisData</span><span class="o">));</span>
    <span class="o">}</span>


    <span class="c1">// 使用空缓存解决缓存穿透问题的取数据方法</span>
    <span class="kd">public</span> <span class="o">&lt;</span><span class="no">R</span><span class="o">,</span> <span class="no">ID</span><span class="o">&gt;</span> <span class="no">R</span> <span class="nf">getWithPassThrough</span><span class="o">(</span><span class="nc">String</span> <span class="n">keyPrefix</span><span class="o">,</span> <span class="no">ID</span> <span class="n">id</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="no">R</span><span class="o">&gt;</span> <span class="n">clazz</span><span class="o">,</span>
                                          <span class="nc">Function</span><span class="o">&lt;</span><span class="no">ID</span><span class="o">,</span><span class="no">R</span><span class="o">&gt;</span> <span class="n">dbQuery</span><span class="o">,</span> <span class="kt">long</span> <span class="n">expireTime</span><span class="o">,</span>
                                          <span class="nc">TimeUnit</span> <span class="n">timeUnit</span><span class="o">)</span> <span class="o">{</span>

        <span class="nc">String</span> <span class="n">key</span> <span class="o">=</span> <span class="n">keyPrefix</span> <span class="o">+</span> <span class="n">id</span><span class="o">;</span>
        <span class="nc">String</span> <span class="n">json</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>
        <span class="c1">// 1. 判断缓存中是否有数据且不是空缓存</span>
        <span class="k">if</span> <span class="o">(</span><span class="nc">StrUtil</span><span class="o">.</span><span class="na">isNotBlank</span><span class="o">(</span><span class="n">json</span><span class="o">))</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">JSONUtil</span><span class="o">.</span><span class="na">toBean</span><span class="o">(</span><span class="n">json</span><span class="o">,</span> <span class="n">clazz</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="c1">// 2.有数据但是是空缓存，说明数据库没有这数据，不要进行穿透访问后续</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">json</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="c1">// 3. 缓存中没有数据,要查数据库确定是否有这数据</span>
        <span class="no">R</span> <span class="n">data</span> <span class="o">=</span> <span class="n">dbQuery</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">data</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 数据库也没有这数据，删除缓存中的空缓存</span>
            <span class="k">this</span><span class="o">.</span><span class="na">setCache</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="s">""</span><span class="o">,</span> <span class="n">expireTime</span><span class="o">,</span> <span class="n">timeUnit</span><span class="o">);</span>
            <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="c1">// 4. 数据库有这数据，将数据存储进入缓存</span>
        <span class="k">this</span><span class="o">.</span><span class="na">setCache</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">data</span><span class="o">,</span> <span class="n">expireTime</span><span class="o">,</span> <span class="n">timeUnit</span><span class="o">);</span>

        <span class="k">return</span> <span class="n">data</span><span class="o">;</span>
    <span class="o">}</span>


    <span class="c1">// 使用逻辑过期时间来缓解缓存击穿，热点key问题,需要启动前加载数据到缓存中</span>
    <span class="kd">public</span> <span class="o">&lt;</span><span class="no">R</span><span class="o">,</span> <span class="no">ID</span><span class="o">&gt;</span> <span class="no">R</span> <span class="nf">getLogicExpire</span><span class="o">(</span><span class="nc">String</span> <span class="n">keyPrefix</span><span class="o">,</span> <span class="no">ID</span> <span class="n">id</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="no">R</span><span class="o">&gt;</span> <span class="n">clazz</span><span class="o">,</span>
                                        <span class="nc">String</span> <span class="n">lockKeyPrefix</span><span class="o">,</span><span class="nc">Long</span> <span class="n">lockExpireTime</span><span class="o">,</span> <span class="nc">TimeUnit</span> <span class="n">lockTimeUnit</span><span class="o">,</span>
                                        <span class="nc">Function</span><span class="o">&lt;</span><span class="no">ID</span><span class="o">,</span><span class="no">R</span><span class="o">&gt;</span> <span class="n">dbQuery</span><span class="o">,</span> <span class="kt">long</span> <span class="n">expireTime</span><span class="o">,</span> <span class="nc">TimeUnit</span> <span class="n">timeUnit</span><span class="o">){</span>
        <span class="c1">// 1. 从Redis缓存中查询</span>
        <span class="nc">String</span> <span class="n">key</span> <span class="o">=</span> <span class="n">keyPrefix</span> <span class="o">+</span> <span class="n">id</span><span class="o">;</span>
        <span class="nc">String</span> <span class="n">redisDataJson</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>
        <span class="c1">// 2. 判断缓存中是否有数据</span>
        <span class="k">if</span> <span class="o">(</span><span class="nc">StrUtil</span><span class="o">.</span><span class="na">isBlank</span><span class="o">(</span><span class="n">redisDataJson</span><span class="o">))</span> <span class="o">{</span>
            <span class="k">return</span> <span class="kc">null</span><span class="o">;</span><span class="c1">// 缓存中没有数据，直接返回失败</span>
        <span class="o">}</span>
        <span class="c1">// 3. 将RedisData转换为Shop</span>
        <span class="nc">RedisData</span> <span class="n">redisData</span> <span class="o">=</span> <span class="nc">JSONUtil</span><span class="o">.</span><span class="na">toBean</span><span class="o">(</span><span class="n">redisDataJson</span><span class="o">,</span> <span class="nc">RedisData</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="c1">// 这一步将RedisDataJson转换为RedisData对象</span>
        <span class="nc">LocalDateTime</span> <span class="n">jsonExpireTime</span> <span class="o">=</span> <span class="n">redisData</span><span class="o">.</span><span class="na">getExpireTime</span><span class="o">();</span> <span class="c1">// 这一步获取RedisData对象中的过期时间</span>
        <span class="no">R</span> <span class="n">result</span> <span class="o">=</span> <span class="nc">JSONUtil</span><span class="o">.</span><span class="na">toBean</span><span class="o">((</span><span class="nc">JSONObject</span><span class="o">)</span> <span class="n">redisData</span><span class="o">.</span><span class="na">getData</span><span class="o">(),</span> <span class="n">clazz</span><span class="o">);</span> <span class="c1">// 这一步将RedisData对象中的数据转换为Shop对象 JSONUtils.toBean()是将JSONObject转换为Shop对象的工具方法</span>
        <span class="c1">// 4. 判断缓存是否过期 未过期，则直接返回结果即可</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">jsonExpireTime</span><span class="o">.</span><span class="na">isAfter</span><span class="o">(</span><span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">()))</span> <span class="o">{</span>
<span class="c1">//            System.out.println("缓存未过期");</span>
            <span class="k">return</span> <span class="n">result</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="c1">// 5. 如果过期，需要缓存重建，使用互斥锁来进行重建</span>
        <span class="nc">String</span> <span class="n">lockKey</span> <span class="o">=</span> <span class="n">lockKeyPrefix</span> <span class="o">+</span> <span class="n">id</span><span class="o">;</span>
        <span class="kt">boolean</span> <span class="n">islock</span> <span class="o">=</span> <span class="n">tryLock</span><span class="o">(</span><span class="n">lockKey</span><span class="o">,</span> <span class="n">lockExpireTime</span><span class="o">,</span> <span class="n">lockTimeUnit</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">islock</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//            System.out.println("获取锁成功");</span>
            <span class="c1">// 如果获取成功，申请一个线程从线程池，用另一个线程进行更新缓存</span>
            <span class="no">CACHE_REBUILD_EXECUTOR</span><span class="o">.</span><span class="na">submit</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="o">{</span>
                <span class="k">try</span> <span class="o">{</span>
                    <span class="k">this</span><span class="o">.</span><span class="na">setCacheWithLogicExpire</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">dbQuery</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">id</span><span class="o">),</span> <span class="n">expireTime</span><span class="o">,</span> <span class="n">timeUnit</span><span class="o">);</span>
                <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">){</span>
                    <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
                <span class="o">}</span><span class="k">finally</span> <span class="o">{</span>
                    <span class="c1">// 8. 释放互斥锁</span>
                    <span class="n">unlock</span><span class="o">(</span><span class="n">lockKey</span><span class="o">);</span>
                <span class="o">}</span>
            <span class="o">});</span>
        <span class="o">}</span>
        <span class="c1">// 未获得锁的线程，直接使用旧缓存数据</span>
        <span class="k">return</span> <span class="n">result</span><span class="o">;</span>
    <span class="o">}</span>


    <span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">tryLock</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span><span class="nc">Long</span> <span class="n">lockTtl</span><span class="o">,</span><span class="nc">TimeUnit</span> <span class="n">timeUnit</span><span class="o">){</span>
        <span class="nc">Boolean</span> <span class="n">success</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">setIfAbsent</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="s">"1"</span><span class="o">,</span> <span class="n">lockTtl</span><span class="o">,</span> <span class="n">timeUnit</span><span class="o">);</span>
        <span class="k">return</span> <span class="nc">BooleanUtil</span><span class="o">.</span><span class="na">isTrue</span><span class="o">(</span><span class="n">success</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">unlock</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">){</span>
        <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">delete</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>


</code></pre></div></div>

<ol>
  <li>缓存工具类调用</li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ShopServiceImpl</span> <span class="kd">extends</span> <span class="nc">ServiceImpl</span><span class="o">&lt;</span><span class="nc">ShopMapper</span><span class="o">,</span> <span class="nc">Shop</span><span class="o">&gt;</span> <span class="kd">implements</span> <span class="nc">IShopService</span> <span class="o">{</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">StringRedisTemplate</span> <span class="n">stringRedisTemplate</span><span class="o">;</span>

<span class="c1">//    // 逻辑过期缓存时，调用线程进行缓存重建 通过线程池来进行，避免频繁线程销毁申请问题</span>
<span class="c1">//    private final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">CacheClient</span> <span class="n">cacheClient</span><span class="o">;</span>


    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">Result</span> <span class="nf">queryById</span><span class="o">(</span><span class="nc">Long</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//        Shop shop = cacheClient.getWithPassThrough(CACHE_SHOP_KEY, id, Shop.class,</span>
<span class="c1">//                this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);</span>
        <span class="nc">Shop</span> <span class="n">shop</span> <span class="o">=</span> <span class="n">cacheClient</span><span class="o">.</span><span class="na">getLogicExpire</span><span class="o">(</span><span class="no">CACHE_SHOP_KEY</span><span class="o">,</span> <span class="n">id</span><span class="o">,</span> <span class="nc">Shop</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                <span class="no">LOCK_SHOP_KEY</span><span class="o">,</span> <span class="no">LOCK_SHOP_TTL</span><span class="o">,</span> <span class="nc">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">,</span>
                <span class="k">this</span><span class="o">::</span><span class="n">getById</span><span class="o">,</span> <span class="no">CACHE_SHOP_TTL</span><span class="o">,</span> <span class="nc">TimeUnit</span><span class="o">.</span><span class="na">MINUTES</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">shop</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"店铺不存在"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">shop</span><span class="o">);</span>
    <span class="o">}</span>

<span class="o">}</span>

</code></pre></div></div>

<h3 id="45-优惠卷秒杀部分redis技术">4.5 优惠卷秒杀部分Redis技术</h3>

<h4 id="451-全局id介绍">4.5.1 全局ID介绍</h4>

<ul>
  <li>全局ID：用于生成唯一的全局ID，在分布式系统中，保证ID与任一个节点的ID不冲突。
<img src="/assets/images/posts/redis-heima-course/1779935361748.webp" alt="" /></li>
</ul>

<ol>
  <li>全局ID生成器</li>
</ol>

<ul>
  <li>全局ID生成器：用于生成唯一的全局ID，在分布式系统中，保证ID与任一个节点的ID不冲突。</li>
  <li>全局ID生成器特点： 唯一性、高可用、高性能、递增性、安全性</li>
</ul>

<blockquote>
  <p>使用Redis 的自增效果（Redis 在系统中并发访问，唯一存在保证）。通过Redis自增+拼接信息，生成唯一的全局ID。</p>
</blockquote>

<blockquote>
  <p>全局ID生成器策略： UUID 、 Redis自增 、 snowflake算法</p>
</blockquote>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kn">package</span> <span class="nn">com.hmdp.utils</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.StringRedisTemplate</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.stereotype.Component</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">java.time.LocalDateTime</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.time.ZoneOffset</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.time.format.DateTimeFormatter</span><span class="o">;</span>

<span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RedisIdWorker</span> <span class="o">{</span>

    <span class="kd">private</span>  <span class="kd">static</span> <span class="kd">final</span> <span class="kt">long</span> <span class="no">START_TIMESTAMP</span> <span class="o">=</span> <span class="mi">1694502400000L</span><span class="o">;</span>
    <span class="kd">private</span>  <span class="kd">static</span> <span class="kd">final</span> <span class="kt">long</span> <span class="no">TIMESTAMP_BIT</span> <span class="o">=</span> <span class="mi">32</span><span class="o">;</span>

    <span class="kd">private</span> <span class="nc">StringRedisTemplate</span> <span class="n">stringRedisTemplate</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">RedisIdWorker</span><span class="o">(</span><span class="nc">StringRedisTemplate</span> <span class="n">stringRedisTemplate</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">stringRedisTemplate</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kt">long</span> <span class="nf">nextId</span><span class="o">(</span><span class="nc">String</span> <span class="n">keyPrefix</span><span class="o">)</span> <span class="o">{</span>

        <span class="c1">// 1. 生成时间戳</span>
        <span class="nc">LocalDateTime</span> <span class="n">localDateTime</span> <span class="o">=</span> <span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">();</span>
        <span class="nc">Long</span> <span class="n">nowSecond</span> <span class="o">=</span> <span class="n">localDateTime</span><span class="o">.</span><span class="na">toEpochSecond</span><span class="o">(</span><span class="nc">ZoneOffset</span><span class="o">.</span><span class="na">UTC</span><span class="o">);</span>
        <span class="nc">Long</span> <span class="n">timestamp</span> <span class="o">=</span> <span class="n">nowSecond</span> <span class="o">-</span> <span class="no">START_TIMESTAMP</span><span class="o">;</span>

        <span class="c1">// 2. 生成序列号</span>
        <span class="nc">String</span> <span class="n">now</span> <span class="o">=</span> <span class="n">localDateTime</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="nc">DateTimeFormatter</span><span class="o">.</span><span class="na">ofPattern</span><span class="o">(</span><span class="s">"yyyy:MM:dd"</span><span class="o">));</span>
        <span class="nc">Long</span> <span class="n">sequence</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">increment</span><span class="o">(</span><span class="s">"ICR:"</span><span class="o">+</span><span class="n">keyPrefix</span><span class="o">+</span><span class="s">":"</span><span class="o">+</span><span class="n">now</span><span class="o">);</span>

        <span class="c1">// 3. 生成全局ID</span>
        <span class="nc">Long</span> <span class="n">id</span> <span class="o">=</span> <span class="n">timestamp</span> <span class="o">&lt;&lt;</span> <span class="no">TIMESTAMP_BIT</span> <span class="o">|</span> <span class="n">sequence</span><span class="o">;</span>

        <span class="k">return</span> <span class="n">id</span><span class="o">;</span>

    <span class="o">}</span>
<span class="o">}</span>

</code></pre></div></div>

<h3 id="452-秒杀券下单">4.5.2 秒杀券下单</h3>

<ul>
  <li>业务逻辑：</li>
  <li><img src="/assets/images/posts/redis-heima-course/1779949717227.webp" alt="" /></li>
  <li>超卖问题：</li>
  <li><img src="/assets/images/posts/redis-heima-course/1779950977524.webp" alt="" /></li>
  <li><img src="/assets/images/posts/redis-heima-course/1779953418719.webp" alt="" /></li>
  <li>乐观锁策略：</li>
  <li><img src="/assets/images/posts/redis-heima-course/1779953705739.webp" alt="" /></li>
  <li><img src="/assets/images/posts/redis-heima-course/1779953758788.webp" alt="" /></li>
  <li>乐观锁能够保证业务逻辑的 一致性，避免超卖问题。但是会引发请求大量失败的可能性，导致系统性能下降。</li>
  <li>但是可以设置乐观锁的条件，改善性能。</li>
  <li><strong>如下代码，使用剩余库存是否大于0 来判断是否超卖。如果剩余库存为0，说明超卖，直接返回失败。</strong></li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.hmdp.service.impl</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">com.hmdp.dto.Result</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.entity.SeckillVoucher</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.entity.VoucherOrder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.mapper.SeckillVoucherMapper</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.mapper.VoucherOrderMapper</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.service.ISeckillVoucherService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.service.IVoucherOrderService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.extension.service.impl.ServiceImpl</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.utils.RedisIdWorker</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.utils.UserHolder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.StringRedisTemplate</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.stereotype.Service</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.transaction.annotation.Transactional</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">javax.annotation.Resource</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.time.LocalDateTime</span><span class="o">;</span>

<span class="nd">@Service</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">VoucherOrderServiceImpl</span> <span class="kd">extends</span> <span class="nc">ServiceImpl</span><span class="o">&lt;</span><span class="nc">VoucherOrderMapper</span><span class="o">,</span> <span class="nc">VoucherOrder</span><span class="o">&gt;</span> <span class="kd">implements</span> <span class="nc">IVoucherOrderService</span> <span class="o">{</span>
    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">StringRedisTemplate</span> <span class="n">stringRedisTemplate</span><span class="o">;</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">RedisIdWorker</span> <span class="n">redisIdWorker</span><span class="o">;</span> <span class="c1">// 用于生成订单id</span>

    <span class="c1">// 注入秒杀优惠卷Mapper</span>
    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">ISeckillVoucherService</span> <span class="n">seckillVoucherService</span><span class="o">;</span>


    <span class="nd">@Override</span>
    <span class="nd">@Transactional</span><span class="o">(</span><span class="n">rollbackFor</span> <span class="o">=</span> <span class="nc">Exception</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
    <span class="kd">public</span> <span class="nc">Result</span> <span class="nf">seckillVoucher</span><span class="o">(</span><span class="nc">Long</span> <span class="n">voucherId</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// 1 .查询秒杀优惠卷</span>
        <span class="nc">SeckillVoucher</span> <span class="n">seckillvoucher</span> <span class="o">=</span> <span class="n">seckillVoucherService</span><span class="o">.</span><span class="na">getById</span><span class="o">(</span><span class="n">voucherId</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">seckillvoucher</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"优惠卷不存在"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="nc">Integer</span> <span class="n">stock</span> <span class="o">=</span> <span class="n">seckillvoucher</span><span class="o">.</span><span class="na">getStock</span><span class="o">();</span>
        <span class="nc">LocalDateTime</span> <span class="n">startTime</span> <span class="o">=</span> <span class="n">seckillvoucher</span><span class="o">.</span><span class="na">getBeginTime</span><span class="o">();</span>
        <span class="nc">LocalDateTime</span> <span class="n">endTime</span> <span class="o">=</span> <span class="n">seckillvoucher</span><span class="o">.</span><span class="na">getEndTime</span><span class="o">();</span>
        <span class="nc">LocalDateTime</span> <span class="n">now</span> <span class="o">=</span> <span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">();</span>
        <span class="c1">// 2. 判断秒杀是否开始</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">now</span><span class="o">.</span><span class="na">isBefore</span><span class="o">(</span><span class="n">startTime</span><span class="o">))</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"秒杀优惠卷未开始"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="c1">// 3. 判断秒杀是否已经结束</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">now</span><span class="o">.</span><span class="na">isAfter</span><span class="o">(</span><span class="n">endTime</span><span class="o">))</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"秒杀优惠卷已结束"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="c1">// 4. 判断优惠卷是否已经售罄</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">stock</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"秒杀优惠卷已售罄"</span><span class="o">);</span>
        <span class="o">}</span>

        <span class="c1">// 创建和扣减是一个原子操作，不能拆开</span>
        <span class="c1">// 5. 扣减优惠卷库存</span>
        <span class="n">seckillvoucher</span><span class="o">.</span><span class="na">setStock</span><span class="o">(</span><span class="n">stock</span> <span class="o">-</span> <span class="mi">1</span><span class="o">);</span>
        <span class="kt">boolean</span> <span class="n">result</span> <span class="o">=</span> <span class="n">seckillVoucherService</span><span class="o">.</span><span class="na">update</span><span class="o">().</span>
                <span class="n">setSql</span><span class="o">(</span><span class="s">"stock = stock - 1"</span><span class="o">).</span><span class="na">eq</span><span class="o">(</span><span class="s">"voucher_id"</span><span class="o">,</span> <span class="n">voucherId</span><span class="o">).</span><span class="na">gt</span><span class="o">(</span><span class="s">"stock"</span><span class="o">,</span> <span class="mi">0</span><span class="o">)</span>
                <span class="o">.</span><span class="na">update</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">result</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"秒杀优惠卷已售罄"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="c1">// 6. 创建订单</span>
        <span class="c1">// 获取订单ID 、 用户ID 、 优惠卷ID</span>

        <span class="nc">Long</span> <span class="n">orderId</span> <span class="o">=</span> <span class="n">redisIdWorker</span><span class="o">.</span><span class="na">nextId</span><span class="o">(</span><span class="s">"order"</span><span class="o">);</span>
<span class="c1">//        System.out.println("订单id：" + orderId);</span>
        <span class="c1">// 用户ID 在拦截器中进行获取</span>
        <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="nc">UserHolder</span><span class="o">.</span><span class="na">getUser</span><span class="o">().</span><span class="na">getId</span><span class="o">();</span>

        <span class="c1">// 插入订单数据入数据库</span>
        <span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">VoucherOrder</span><span class="o">();</span>
        <span class="n">voucherOrder</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">orderId</span><span class="o">);</span>
        <span class="n">voucherOrder</span><span class="o">.</span><span class="na">setUserId</span><span class="o">(</span><span class="n">userID</span><span class="o">);</span>
        <span class="n">voucherOrder</span><span class="o">.</span><span class="na">setVoucherId</span><span class="o">(</span><span class="n">voucherId</span><span class="o">);</span>
        <span class="n">voucherOrder</span><span class="o">.</span><span class="na">setCreateTime</span><span class="o">(</span><span class="n">now</span><span class="o">);</span>

        <span class="n">save</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>
        <span class="c1">// 7. 返回订单id</span>

        <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">orderId</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>



</code></pre></div></div>

<ul>
  <li>秒杀券对于每个用户只能下单一次的设计参考：
<a href="/posts/redis-seckill-lock/">秒杀券一人一单下单设计介绍</a>
<img src="/assets/images/posts/redis-heima-course/1780019888863.webp" alt="" /></li>
</ul>

<blockquote>
  <p>导致上锁失效原因是：JVM锁是通过内部锁监视器来维护的一个常量池
而对于多台JVM，每个JVM都有自己的锁监视器，所以会导致锁失效。
<img src="/assets/images/posts/redis-heima-course/1780020566577.webp" alt="" /></p>
</blockquote>

<ol>
  <li>(同一个用户对同一批数据发起多个请求引发问题)分布式锁：</li>
</ol>

<ul>
  <li>分布式锁是指在多个节点之间协调的锁，用于确保在多个节点之间只能有一个线程或进程能够访问共享资源。</li>
  <li>分布式锁的实现方式有很多种，比如使用Redis的SETNX命令、使用ZookeeperKeeper、使用MySQL的行锁等。</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SET lock threadID EX  10 NX   // 设置锁，过期时间为10秒，如果锁不存在则设置成功
</code></pre></div></div>

<ul>
  <li>存在问题：锁过期时间太短或任务阻塞时间太长，会导致锁失效。</li>
  <li><img src="/assets/images/posts/redis-heima-course/1780023111667.webp" alt="锁过期时间太短或任务阻塞时间太长导致锁失效" /></li>
  <li>解决方案：续命机制，在任务执行过程中，定时续命，延长锁的过期时间。<strong>锁的释放设置标识判断，是否为当前线程的锁。</strong></li>
  <li><img src="/assets/images/posts/redis-heima-course/1780023904004.webp" alt="锁释放时受到阻塞导致异常（需要保证判断锁和释放锁为一个原子操作）" /></li>
</ul>

<hr />

<blockquote>
  <p>Redis提供Lua脚本(它在Redis服务器端执行的，确保了原子性)，用于实现分布式锁的原子操作。</p>
</blockquote>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- 获取锁中key的值(Redis中存储的锁（键值对）)</span>
<span class="kd">local</span> <span class="n">key</span> <span class="o">=</span> <span class="n">KEYS</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="c1">-- 当前线程标识</span>
<span class="kd">local</span> <span class="n">threadID</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>

<span class="c1">--  获取锁中key的值</span>
<span class="kd">local</span> <span class="n">id</span> <span class="o">=</span> <span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"GET"</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>

<span class="c1">-- 判断锁中key的值与当前线程标识是否相等</span>
<span class="k">if</span> <span class="n">id</span> <span class="o">==</span> <span class="n">threadID</span> <span class="k">then</span>
    <span class="c1">-- 释放锁</span>
    <span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"DEL"</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>
    <span class="k">return</span> <span class="mi">1</span>
<span class="k">else</span>
    <span class="c1">-- 锁被其他线程占用，无法释放</span>
    <span class="k">return</span> <span class="mi">0</span>
<span class="k">end</span>

</code></pre></div></div>

<ol>
  <li>Setnx分布式锁的问题
<img src="/assets/images/posts/redis-heima-course/1780284104701.webp" alt="" /></li>
</ol>

<hr />

<blockquote>
  <p>可重入锁是指在同一个线程或进程中，可以重复获取同一个锁，而不会导致死锁。</p>

  <p>假设有一个线程A，它获取了锁L1，然后在执行过程中，需要获取锁L1，而锁L1已经被线程A获取了，所以会阻塞。
但是如果锁L1是可重入锁，那么线程A可以再次获取锁L1，而不会阻塞。</p>
</blockquote>

<blockquote>
  <p>Redission提供可重入锁的实现，用于解决以上问题。</p>
</blockquote>

<h5 id="redisission可重入锁原理">Redisission可重入锁原理</h5>

<ul>
  <li>ReentrantRedisLock（解决可重入锁问题的实现）：redis键值对中不仅存储锁的标识，还存储锁的获取次数。</li>
  <li>再次获取锁时，锁的获取次数会增加。释放锁时，锁的获取次数会减少。</li>
  <li>当锁的获取次数为0时，锁才会被释放。
<img src="/assets/images/posts/redis-heima-course/1780296355559.webp" alt="Redisission可重入锁原理" /></li>
  <li><strong>同时保证原子性，确保在获取锁和释放锁时，不会受到其他线程的干扰。使用Lua脚本实现。</strong></li>
</ul>

<hr />

<ul>
  <li>获取锁Lua脚本：</li>
</ul>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- 获取锁中key的值(Redis中存储的锁（键值对）)</span>
<span class="kd">local</span> <span class="n">key</span> <span class="o">=</span> <span class="n">KEYS</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="c1">-- 当前线程标识</span>
<span class="kd">local</span> <span class="n">threadID</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>

<span class="kd">local</span> <span class="n">releaseTime</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>

<span class="c1">-- 判断是否存在</span>
<span class="k">if</span> <span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"EXISTS"</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">then</span>
    <span class="c1">-- 锁不存在，获取锁</span>
    <span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"HSET"</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">threadID</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"EXPIRE"</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">releaseTime</span><span class="p">)</span> <span class="c1">-- 锁锁过期时间</span>
    <span class="k">return</span> <span class="mi">1</span>
<span class="k">end</span>

<span class="c1">-- 锁存在，判断是否是当前线程获取锁</span>
<span class="k">if</span> <span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"HEXISTS"</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">threadID</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span> <span class="k">then</span>
    <span class="c1">-- 是当前线程获取锁，重入锁</span>
    <span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"HINCRBY"</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">threadID</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"EXPIRE"</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">releaseTime</span><span class="p">)</span> <span class="c1">-- 重置锁锁过期时间</span>
    <span class="k">return</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="k">return</span> <span class="mi">0</span>  <span class="c1">-- 锁存在且不是当前线程获取锁，获取锁失败</span>

</code></pre></div></div>

<ul>
  <li>释放锁Lua脚本：</li>
</ul>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- 获取锁中key的值(Redis中存储的锁（键值对）)</span>
<span class="kd">local</span> <span class="n">key</span> <span class="o">=</span> <span class="n">KEYS</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="c1">-- 当前线程标识</span>
<span class="kd">local</span> <span class="n">threadID</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="c1">-- 锁锁过期时间</span>
<span class="kd">local</span> <span class="n">releaseTime</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>

<span class="c1">-- 判断当前锁是否是当前线程获取锁</span>
<span class="k">if</span> <span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"HEXISTS"</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">threadID</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">then</span>
    <span class="c1">-- 锁不存在或不是当前线程获取锁，无法释放</span>
    <span class="k">return</span> <span class="kc">nil</span>
<span class="k">end</span>

<span class="kd">local</span> <span class="n">count</span> <span class="o">=</span> <span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"HINCRBY"</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">threadID</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="k">if</span> <span class="n">count</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">then</span>
    <span class="c1">-- 锁的获取次数大于0，不释放锁 重置锁锁过期时间</span>
    <span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"EXPIRE"</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">releaseTime</span><span class="p">)</span>
    <span class="k">return</span> <span class="kc">nil</span>
<span class="k">else</span>
    <span class="c1">-- 锁的获取次数为0，释放锁</span>
    <span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"HDEL"</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">threadID</span><span class="p">)</span> 
    <span class="k">return</span> <span class="kc">nil</span>
<span class="k">end</span>
<span class="k">return</span> <span class="kc">nil</span>

</code></pre></div></div>

<h5 id="redisission-重试机制">Redisission 重试机制</h5>

<p><img src="/assets/images/posts/redis-heima-course/1780298596466.webp" alt="Redisission 重试机制和看门狗机制" /></p>

<ul>
  <li>消息订阅、信号量实现重试机制、最大重试时间</li>
  <li>当客户端获取锁时，如果无法获取到，会监听锁的消息，当锁被释放时，并且在最大重试时间内，会重试获取锁。</li>
</ul>

<h5 id="redisission-锁过期的安全问题">Redisission 锁过期的安全问题</h5>

<ul>
  <li>当任务获取锁成功，但是由于任务阻塞未完成，超时释放锁，会导致锁失效。</li>
  <li>解决方案：续命机制，在任务执行过程中，定时续命，延长锁的过期时间。<strong>锁的释放设置标识判断，是否为当前线程的锁。</strong></li>
  <li>看门狗机制：在任务执行过程中，定时检查锁是否过期，如果过期，会自动释放锁。</li>
</ul>

<h5 id="redisission-主从一致性问题解决">Redisission 主从一致性问题解决</h5>

<ul>
  <li>主从一致性问题：Redis主机在写入数据时发生异常宕机，从机会继续写入数据，导致主从数据不一致。
<img src="/assets/images/posts/redis-heima-course/1780299092324.webp" alt="" /></li>
  <li>解决方案： MultiLock方式，创建锁需要在所有节点上创建锁，确保锁的创建在所有节点上都生效。如果其中有一个节点创建锁失败，会创建失败。</li>
</ul>

<h3 id="453-秒杀券的异步操作消息队列">4.5.3 秒杀券的异步操作（消息队列）</h3>

<p><img src="/assets/images/posts/redis-heima-course/1780301583577.webp" alt="" />
<img src="/assets/images/posts/redis-heima-course/1780301862185.webp" alt="" /></p>

<ul>
  <li>Redis中存储秒杀券的库存数量。</li>
  <li>Redis中存储秒杀券的订单队列（Set结构保证一人一单）。在添加进入队列这个操作同样需要原子操作，确保队列添加和修改库存的操作是原子的。</li>
</ul>

<ol>
  <li>Redis中记录秒杀券的库存数量 和存储秒杀券的订单队列（订单中用户ID和秒杀券ID）Set结构保证一人一单、原子操作</li>
</ol>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">---</span>
<span class="c1">--- Generated by EmmyLua(https://github.com/EmmyLua)</span>
<span class="c1">--- Created by 123.</span>
<span class="c1">--- DateTime: 2026/6/1 17:02</span>
<span class="c1">---</span>

<span class="c1">--  获取优惠券ID</span>
<span class="kd">local</span> <span class="n">voucherID</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="c1">-- 用户ID</span>
<span class="kd">local</span> <span class="n">userID</span> <span class="o">=</span> <span class="n">ARGV</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>

<span class="c1">-- 操作数据</span>
<span class="c1">-- 库存Key</span>
<span class="kd">local</span> <span class="n">stockKey</span> <span class="o">=</span> <span class="s1">'seckill:stock:'</span> <span class="o">..</span> <span class="n">voucherID</span>
<span class="c1">-- 订单Key</span>
<span class="kd">local</span> <span class="n">orderKey</span> <span class="o">=</span> <span class="s1">'seckill:order:'</span> <span class="o">..</span> <span class="n">voucherID</span>

<span class="c1">-- 脚本业务</span>
<span class="c1">-- 判断库存是否充足，不足则返回失败</span>
<span class="kd">local</span> <span class="n">stock</span> <span class="o">=</span> <span class="nb">tonumber</span><span class="p">(</span><span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'GET'</span><span class="p">,</span> <span class="n">stockKey</span><span class="p">))</span>
<span class="k">if</span> <span class="n">stock</span> <span class="o">==</span> <span class="kc">nil</span> <span class="ow">or</span> <span class="n">stock</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="k">then</span>

    <span class="k">return</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="c1">-- 库存充足，判断用户是否已经下单（限制一人一单）</span>
<span class="k">if</span><span class="p">(</span><span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'sismember'</span><span class="p">,</span> <span class="n">orderKey</span><span class="p">,</span> <span class="n">userID</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="k">then</span>
<span class="c1">--     标识用户已经下过单</span>

    <span class="k">return</span> <span class="mi">2</span>
<span class="k">end</span>

<span class="c1">-- 用户没有下过单，且库存充足，需要减库存，添加订单</span>
<span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'incrby'</span><span class="p">,</span> <span class="n">stockKey</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">redis</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s1">'sadd'</span><span class="p">,</span> <span class="n">orderKey</span><span class="p">,</span> <span class="n">userID</span><span class="p">)</span>
<span class="k">return</span> <span class="mi">0</span>


</code></pre></div></div>

<ol>
  <li>秒杀券的异步操作（消息队列）：</li>
</ol>

<ul>
  <li>线程池：用于处理秒杀订单的异步操作。</li>
  <li>消息队列：用于存储秒杀订单的异步操作。</li>
  <li>线程任务设置：用于处理秒杀订单的异步操作。</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.hmdp.service.impl</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">lombok.extern.slf4j.Slf4j</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.core.io.ClassPathResource</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.dto.Result</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.entity.VoucherOrder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.mapper.VoucherOrderMapper</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.service.ISeckillVoucherService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.service.IVoucherOrderService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.extension.service.impl.ServiceImpl</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.utils.RedisIdWorker</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.utils.UserHolder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.redisson.api.RLock</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.redisson.api.RedissonClient</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.aop.framework.AopContext</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.StringRedisTemplate</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.script.DefaultRedisScript</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.stereotype.Service</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.transaction.annotation.Transactional</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">javax.annotation.PostConstruct</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.annotation.Resource</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Collections</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.concurrent.ArrayBlockingQueue</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.concurrent.BlockingQueue</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.concurrent.ExecutorService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.concurrent.Executors</span><span class="o">;</span>

<span class="cm">/**
 * &lt;p&gt;
 *  服务实现类
 * &lt;/p&gt;
 *
 * @author 虎哥
 * @since 2021-12-22
 */</span>
<span class="nd">@Slf4j</span>
<span class="nd">@Service</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">VoucherOrderServiceImpl</span> <span class="kd">extends</span> <span class="nc">ServiceImpl</span><span class="o">&lt;</span><span class="nc">VoucherOrderMapper</span><span class="o">,</span> <span class="nc">VoucherOrder</span><span class="o">&gt;</span> <span class="kd">implements</span> <span class="nc">IVoucherOrderService</span> <span class="o">{</span>
    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">StringRedisTemplate</span> <span class="n">stringRedisTemplate</span><span class="o">;</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">RedisIdWorker</span> <span class="n">redisIdWorker</span><span class="o">;</span> <span class="c1">// 用于生成订单id</span>

    <span class="c1">// 注入秒杀优惠卷Mapper</span>
    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">ISeckillVoucherService</span> <span class="n">seckillVoucherService</span><span class="o">;</span>

    <span class="c1">// 注入RedissonClient</span>
    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">RedissonClient</span> <span class="n">redissonClient</span><span class="o">;</span>

    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">DefaultRedisScript</span><span class="o">&lt;</span><span class="nc">Long</span><span class="o">&gt;</span> <span class="no">SECKILL_SCRIPT</span><span class="o">;</span>
    <span class="kd">static</span> <span class="o">{</span>
        <span class="no">SECKILL_SCRIPT</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultRedisScript</span><span class="o">&lt;&gt;();</span>
        <span class="no">SECKILL_SCRIPT</span><span class="o">.</span><span class="na">setLocation</span><span class="o">(</span><span class="k">new</span> <span class="nc">ClassPathResource</span><span class="o">(</span><span class="s">"seckill.lua"</span><span class="o">));</span>
        <span class="no">SECKILL_SCRIPT</span><span class="o">.</span><span class="na">setResultType</span><span class="o">(</span><span class="nc">Long</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="c1">//    阻塞队列</span>
    <span class="kd">private</span> <span class="nc">BlockingQueue</span><span class="o">&lt;</span><span class="nc">VoucherOrder</span><span class="o">&gt;</span> <span class="n">voucherOrderBlockingQueue</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayBlockingQueue</span><span class="o">&lt;</span><span class="nc">VoucherOrder</span><span class="o">&gt;(</span><span class="mi">1024</span><span class="o">*</span><span class="mi">1024</span><span class="o">);</span>
    <span class="c1">// 创建线程池，线程用于完成队列中的任务</span>
    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">ExecutorService</span> <span class="no">SECKILL_EXECUTOR_SERVICE</span> <span class="o">=</span> <span class="nc">Executors</span><span class="o">.</span><span class="na">newFixedThreadPool</span><span class="o">(</span><span class="mi">10</span><span class="o">);</span>

    <span class="nd">@PostConstruct</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">()</span> <span class="o">{</span> <span class="c1">// 将订单处理线程提交到线程池中</span>
        <span class="no">SECKILL_EXECUTOR_SERVICE</span><span class="o">.</span><span class="na">submit</span><span class="o">(</span><span class="k">new</span> <span class="nc">VocherOrderHandler</span><span class="o">());</span>
    <span class="o">}</span>
    <span class="c1">// 订单处理线程,用于处理队列中的订单</span>
    <span class="kd">private</span> <span class="kd">class</span> <span class="nc">VocherOrderHandler</span> <span class="kd">implements</span> <span class="nc">Runnable</span> <span class="o">{</span>
        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
            <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">try</span><span class="o">{</span>
                    <span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span> <span class="o">=</span> <span class="n">voucherOrderBlockingQueue</span><span class="o">.</span><span class="na">take</span><span class="o">();</span>
                    <span class="n">handlerVoucherOrder</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>
                <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">){</span>
                    <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"订单处理线程被中断"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
                <span class="o">}</span>
            <span class="o">}</span>

        <span class="o">}</span>
    <span class="o">}</span>
    <span class="c1">// 获取当前类的代理对象，用于子线程在创建订单时调用createOrder方法创建订单</span>
    <span class="kd">private</span> <span class="nc">IVoucherOrderService</span> <span class="n">proxy</span><span class="o">;</span>

    <span class="c1">// 处理订单，由线程池中子线程创建订单</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">handlerVoucherOrder</span><span class="o">(</span><span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// 1. 获取用户ID</span>
        <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="n">voucherOrder</span><span class="o">.</span><span class="na">getUserId</span><span class="o">();</span>
        <span class="c1">// 2. 创建锁对象</span>
        <span class="nc">RLock</span> <span class="n">lock</span> <span class="o">=</span> <span class="n">redissonClient</span><span class="o">.</span><span class="na">getLock</span><span class="o">(</span><span class="s">"lock:order:"</span> <span class="o">+</span> <span class="n">userID</span><span class="o">);</span>
        <span class="c1">// 获取锁</span>
        <span class="kt">boolean</span> <span class="n">locked</span> <span class="o">=</span> <span class="n">lock</span><span class="o">.</span><span class="na">tryLock</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">locked</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span><span class="o">+</span><span class="n">userID</span><span class="o">+</span><span class="s">"}下单失败，获取锁失败"</span><span class="o">);</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="c1">// 3. 使用代理对象创建订单</span>
        <span class="k">try</span><span class="o">{</span>
            <span class="n">proxy</span><span class="o">.</span><span class="na">createOrder</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>
        <span class="o">}</span><span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">e</span><span class="o">){</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span><span class="o">+</span><span class="n">userID</span><span class="o">+</span><span class="s">"}下单失败，创建订单失败"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>

        <span class="o">}</span><span class="k">finally</span> <span class="o">{</span>
        <span class="c1">// 4. 释放锁</span>
        <span class="n">lock</span><span class="o">.</span><span class="na">unlock</span><span class="o">();</span>
               <span class="o">}</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">Result</span> <span class="nf">seckillVoucher</span><span class="o">(</span><span class="nc">Long</span> <span class="n">voucherId</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="nc">UserHolder</span><span class="o">.</span><span class="na">getUser</span><span class="o">().</span><span class="na">getId</span><span class="o">();</span>
        <span class="c1">// 1. 执行lua脚本，看返回情况 0表示有资格</span>
        <span class="nc">Long</span> <span class="n">result</span> <span class="o">=</span> <span class="n">stringRedisTemplate</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="no">SECKILL_SCRIPT</span><span class="o">,</span>
                <span class="nc">Collections</span><span class="o">.</span><span class="na">emptyList</span><span class="o">(),</span>
                <span class="n">voucherId</span><span class="o">.</span><span class="na">toString</span><span class="o">(),</span>
                <span class="n">userID</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span>
        <span class="c1">// 2. 判断结果是否为0</span>
        <span class="kt">int</span> <span class="n">r</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="na">intValue</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">r</span> <span class="o">==</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"库存不足"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">r</span> <span class="o">==</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"您已下单过"</span><span class="o">);</span>
        <span class="o">}</span>

        <span class="c1">// 3. 创建订单 并将订单放入阻塞队列中去</span>
        <span class="nc">Long</span> <span class="n">orderID</span> <span class="o">=</span> <span class="n">redisIdWorker</span><span class="o">.</span><span class="na">nextId</span><span class="o">(</span><span class="s">"order"</span><span class="o">);</span>
        <span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">VoucherOrder</span><span class="o">();</span>
        <span class="n">voucherOrder</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">orderID</span><span class="o">);</span>
        <span class="n">voucherOrder</span><span class="o">.</span><span class="na">setUserId</span><span class="o">(</span><span class="n">userID</span><span class="o">);</span>
        <span class="n">voucherOrder</span><span class="o">.</span><span class="na">setVoucherId</span><span class="o">(</span><span class="n">voucherId</span><span class="o">);</span>
        <span class="n">voucherOrderBlockingQueue</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>

        <span class="n">proxy</span> <span class="o">=</span> <span class="o">(</span><span class="nc">IVoucherOrderService</span><span class="o">)</span> <span class="nc">AopContext</span><span class="o">.</span><span class="na">currentProxy</span><span class="o">();</span>
<span class="c1">//</span>
        <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">orderID</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="nd">@Transactional</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">createOrder</span><span class="o">(</span><span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// 新增限制一人一单的判断</span>
        <span class="nc">Long</span> <span class="n">userID</span> <span class="o">=</span> <span class="n">voucherOrder</span><span class="o">.</span><span class="na">getUserId</span><span class="o">();</span>
        <span class="nc">Long</span> <span class="n">voucherId</span> <span class="o">=</span> <span class="n">voucherOrder</span><span class="o">.</span><span class="na">getVoucherId</span><span class="o">();</span>

        <span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="n">query</span><span class="o">().</span><span class="na">eq</span><span class="o">(</span><span class="s">"user_id"</span><span class="o">,</span> <span class="n">userID</span><span class="o">).</span><span class="na">eq</span><span class="o">(</span><span class="s">"voucher_id"</span><span class="o">,</span> <span class="n">voucherId</span><span class="o">).</span><span class="na">count</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">count</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
           <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span><span class="o">+</span><span class="n">userID</span><span class="o">+</span><span class="s">"}已购买过该优惠卷"</span><span class="o">);</span>
           <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>


        <span class="c1">// 创建和扣减是一个原子操作，不能拆开</span>
        <span class="c1">// 5. 扣减优惠卷库存</span>
        <span class="kt">boolean</span> <span class="n">result</span> <span class="o">=</span> <span class="n">seckillVoucherService</span><span class="o">.</span><span class="na">update</span><span class="o">().</span>
                <span class="n">setSql</span><span class="o">(</span><span class="s">"stock = stock - 1"</span><span class="o">).</span><span class="na">eq</span><span class="o">(</span><span class="s">"voucher_id"</span><span class="o">,</span> <span class="n">voucherId</span><span class="o">).</span><span class="na">gt</span><span class="o">(</span><span class="s">"stock"</span><span class="o">,</span> <span class="mi">0</span><span class="o">)</span>
                <span class="o">.</span><span class="na">update</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">result</span><span class="o">)</span> <span class="o">{</span>
           <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"用户{"</span><span class="o">+</span><span class="n">userID</span><span class="o">+</span><span class="s">"}下单失败，优惠卷已售罄"</span><span class="o">);</span>
           <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="c1">// 6. 创建订单</span>
        <span class="c1">// 获取订单ID 、 用户ID 、 优惠卷ID</span>

        <span class="nc">Long</span> <span class="n">orderId</span> <span class="o">=</span> <span class="n">redisIdWorker</span><span class="o">.</span><span class="na">nextId</span><span class="o">(</span><span class="s">"order"</span><span class="o">);</span>
        <span class="c1">// System.out.println("订单id：" + orderId);</span>
        <span class="c1">// 用户ID 在拦截器中进行获取</span>

        <span class="n">save</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>


    <span class="o">}</span>

<span class="o">}</span>


</code></pre></div></div>

<p><a href="/posts/redis-async-seckill/">Redis黑马点评：秒杀券异步操作：理解消息队列与Lua脚本的结合</a></p>

<h3 id="46-博客点赞功能">4.6 博客点赞功能</h3>

<ul>
  <li>博客点赞功能：用户可以对博客进行点赞，点赞后博客的点赞数会增加。再次点赞会减少点赞数。</li>
  <li>点赞列表：用户可以查看自己点赞的博客列表，按照点赞时间排序。</li>
  <li>实现分析：
    <ul>
      <li>
        <ol>
          <li>Redis使用Sorted Set数据结构实现博客点赞功能。</li>
        </ol>
      </li>
      <li>
        <ol>
          <li>点赞列表：用户可以查看自己点赞的博客列表，按照点赞时间排序。</li>
        </ol>
      </li>
      <li>
        <ol>
          <li>Redis缓存：用户点赞后，需要更新Redis缓存中的点赞数。用户点赞列表需要从Redis缓存中获取。</li>
        </ol>
      </li>
    </ul>
  </li>
</ul>

<h3 id="47-关注功能与推荐功能">4.7 关注功能与推荐功能</h3>

<ul>
  <li>关注功能：用户可以关注其他用户，关注后用户可以查看其他用户的博客。</li>
  <li>关注内容推送：用户关注的用户发布博客后，用户会收到通知。除此之外，用户还可以查看自己关注的用户的博客。</li>
  <li><img src="/assets/images/posts/redis-heima-course/1782457786545.webp" alt="" /></li>
  <li><img src="/assets/images/posts/redis-heima-course/1782458079995.webp" alt="" /></li>
  <li>关注推送，推送具体可采用Redis的Sorted Set数据结构存储接受或者发送的博客。</li>
  <li>但是接收方再从Sorted Set中获取博客时，需要根据博客的发布时间进行排序同时根据时间进行分页。分页注意设计<strong>滚动分页</strong>。</li>
</ul>

<h3 id="48-附近商铺">4.8 附近商铺</h3>

<h4 id="geo-数据结构">GEO 数据结构</h4>

<p><img src="/assets/images/posts/redis-heima-course/1785486299711.webp" alt="" /></p>

<h3 id="49-签到功能">4.9 签到功能</h3>

<h4 id="bitmap-数据结构">BitMap 数据结构</h4>

<p><img src="/assets/images/posts/redis-heima-course/1785489741455.webp" alt="" />
<img src="/assets/images/posts/redis-heima-course/1785489762656.webp" alt="" /></p>

<h3 id="410-uv-统计">4.10 UV 统计</h3>

<h4 id="hyperloglog-数据结构">HyperLogLog 数据结构</h4>

<ul>
  <li>UV: Unique Visitor, 唯一访客数, 用于统计网站的唯一访客数。1天内访问网站的用户数。多次访问网站，只计数一次。</li>
  <li>PV: Page View, 页面访问量, 用于统计网站的页面访问量。1天内访问网站的页面数。多次访问网站，会计数多次。</li>
</ul>

<p><img src="/assets/images/posts/redis-heima-course/1785738300940.webp" alt="" /></p>

<h2 id="5-redis消息队列">5. Redis消息队列</h2>

<ul>
  <li>
    <p>消息队列：存放消息的结构，用于异步处理任务</p>

    <ul>
      <li>消息队列：存储和管理消息，称为消费代理（message broker）</li>
      <li>生产者：将消息放入队列中</li>
      <li>消费者：从队列中取出消息并进行处理
<img src="/assets/images/posts/redis-heima-course/1780628755849.webp" alt="" /></li>
    </ul>
  </li>
  <li>Redis消息队列的优点：持久化存储，不受JVM内存限制</li>
  <li>Redis消息队列的缺点：消息处理速度必须大于消息生产速度，否则会导致消息积压。只支持单消费者。</li>
</ul>

<h3 id="51-消息队列的实现">5.1 消息队列的实现</h3>

<ul>
  <li>
    <p>Redis消息队列：使用Redis的List数据结构实现消息队列，生产者将消息放入List的右侧，消费者从List的左侧取出消息进行处理。</p>

    <ul>
      <li><img src="/assets/images/posts/redis-heima-course/1780626212987.webp" alt="" /></li>
      <li>只支持单消费者，多个消费者只能有一个消费者处理消息，其他消费者只能等待消息处理完成。</li>
    </ul>
  </li>
  <li>
    <p>Redis消息队列：Pub/Sub模式，生产者将消息发布到指定的频道，消费者订阅该频道，当有消息发布到该频道时，消费者会收到消息并进行处理。</p>

    <ul>
      <li><img src="/assets/images/posts/redis-heima-course/1780626771666.webp" alt="" /></li>
      <li><img src="/assets/images/posts/redis-heima-course/1780626864621.webp" alt="" /></li>
    </ul>
  </li>
  <li>
    <p>Redis消息队列：使用Redis的Stream数据结构实现消息队列，生产者将消息放入Stream中，消费者从Stream中取出消息进行处理。</p>

    <ul>
      <li><img src="/assets/images/posts/redis-heima-course/1780627200142.webp" alt="" /></li>
      <li><img src="/assets/images/posts/redis-heima-course/1780627310838.webp" alt="" /></li>
      <li><img src="/assets/images/posts/redis-heima-course/1780627475018.webp" alt="" /></li>
      <li><img src="/assets/images/posts/redis-heima-course/1780627545341.webp" alt="" /></li>
    </ul>
  </li>
  <li>
    <p>Stream 消费者组：多个消费者可以组成一个消费者组，每个消费者组可以处理不同的消息。</p>

    <ul>
      <li><img src="/assets/images/posts/redis-heima-course/1780627713132.webp" alt="" /></li>
      <li><img src="/assets/images/posts/redis-heima-course/1780627831011.webp" alt="" /></li>
      <li><img src="/assets/images/posts/redis-heima-course/1780628036669.webp" alt="" /></li>
      <li>XACK：确认消息处理完成，将消息从Stream中移除。</li>
      <li>XPENDING：查看消费者组中未处理的消息。</li>
      <li><img src="/assets/images/posts/redis-heima-course/1780628668169.webp" alt="" /></li>
    </ul>
  </li>
</ul>

<h2 id="5-分布式缓存集群redis">5. 分布式缓存（集群Redis）</h2>

<p><img src="/assets/images/posts/redis-heima-course/1785738903378.webp" alt="" /></p>

<h3 id="51-redis-持久化策略">5.1 Redis 持久化策略</h3>

<h4 id="511-rdb快照持久化">5.1.1 RDB(快照持久化)</h4>

<ul>
  <li>RDB(快照持久化)：Redis默认的持久化策略，将Redis中的数据定时写入到磁盘中。</li>
</ul>

<p><img src="/assets/images/posts/redis-heima-course/1785739065809.webp" alt="" /></p>

<p><img src="/assets/images/posts/redis-heima-course/1785739234007.webp" alt="" /></p>

<p><img src="/assets/images/posts/redis-heima-course/1785739669569.webp" alt="" /></p>

<h4 id="512-aof追加写入持久化">5.1.2 AOF(追加写入持久化)</h4>

<ul>
  <li>AOF(追加写入持久化)：Redis的另一种持久化策略，将Redis中的数据追加写入到磁盘中。</li>
</ul>

<p><img src="/assets/images/posts/redis-heima-course/1785739921868.webp" alt="" /></p>

<p><img src="/assets/images/posts/redis-heima-course/1785740051513.webp" alt="" /></p>

<p><img src="/assets/images/posts/redis-heima-course/1785740422588.webp" alt="" /></p>

<p><img src="/assets/images/posts/redis-heima-course/1785740525548.webp" alt="" /></p>

<h3 id="52-redis-主从">5.2 Redis 主从</h3>

<p><img src="/assets/images/posts/redis-heima-course/1785740774122.webp" alt="img" /></p>

<h4 id="521-数据同步原理">5.2.1 数据同步原理</h4>

<ul>
  <li>
    <p>全量同步（第一次建立连接主从或无法完成增量同步时）
<img src="/assets/images/posts/redis-heima-course/1785743942589.webp" alt="" />
<img src="/assets/images/posts/redis-heima-course/1785743906461.webp" alt="" /></p>
  </li>
  <li>增量同步（后续建立连接主从时）</li>
  <li>
    <p><img src="/assets/images/posts/redis-heima-course/1785744360241.webp" alt="" /></p>

    <ul>
      <li>优化Redis主从集群</li>
      <li><img src="/assets/images/posts/redis-heima-course/1785744536445.webp" alt="" /></li>
    </ul>
  </li>
</ul>

<h4 id="522-哨兵模式">5.2.2 哨兵模式</h4>

<ul>
  <li>Redis哨兵模式：用于监控Redis主从集群的状态，当主节点发生故障时，自动将从节点提升为主节点，并通知其他从节点进行切换。</li>
</ul>]]></content><author><name>溄</name><email>hongqiang.yan@qq.com</email></author><category term="学习笔记" /><category term="Redis" /><category term="黑马课程" /><category term="学习笔记" /><category term="Java" /><summary type="html"><![CDATA[Redis 黑马课程学习笔记汇总。]]></summary></entry><entry><title type="html">Redis黑马课程学习:秒杀优惠券一人一单——Spring 事务与锁的巧妙配合</title><link href="https://yhongq.github.io/posts/redis-seckill-lock/" rel="alternate" type="text/html" title="Redis黑马课程学习:秒杀优惠券一人一单——Spring 事务与锁的巧妙配合" /><published>2026-05-28T00:00:00+08:00</published><updated>2026-05-28T00:00:00+08:00</updated><id>https://yhongq.github.io/posts/redis-seckill-lock</id><content type="html" xml:base="https://yhongq.github.io/posts/redis-seckill-lock/"><![CDATA[<h1 id="秒杀优惠券一人一单spring-事务与锁的巧妙配合">秒杀优惠券一人一单——Spring 事务与锁的巧妙配合</h1>

<p>在高并发秒杀场景中，实现“一人一单”且保证库存安全扣减，是常见但容易出错的需求。本文通过一段实际代码，剖析如何在 Spring 中结合 <code class="language-plaintext highlighter-rouge">synchronized</code> 锁、<code class="language-plaintext highlighter-rouge">AopContext.currentProxy()</code> 以及 <code class="language-plaintext highlighter-rouge">@Transactional</code> 事务，完成这一逻辑。</p>

<p><a href="https://www.bilibili.com/video/BV1cr4y1671t?vd_source=3495715132f8b407fde241a86af24789&amp;spm_id_from=333.788.player.switch&amp;p=54">实战篇-07.优惠券秒杀-实现一人一单功能_哔哩哔哩_bilibili</a></p>

<p><img src="/assets/images/posts/redis-seckill-lock/1780019902460.webp" alt="1780019902460" /></p>

<hr />

<h2 id="1-背景与设计初衷">1. 背景与设计初衷</h2>

<p>秒杀业务有两个核心难题：</p>

<ul>
  <li><strong>库存超卖</strong>：多线程同时扣减库存，必须保证原子性。</li>
  <li><strong>一人一单</strong>：同一个用户不能重复购买同一优惠券，需要在并发下准确判断。</li>
</ul>

<p>我们很容易想到：扣库存和创建订单应该是一个事务，而判断用户是否已购买和扣库存需要加锁。但如果在业务方法上直接加锁并调用本类的另一个带事务的方法，会遇到 <strong>Spring AOP 自调用事务失效</strong> 的问题。</p>

<blockquote>
  <p>Spring 的 <code class="language-plaintext highlighter-rouge">@Transactional</code> 是通过 <strong>AOP 动态代理</strong> 实现的，具体步骤：</p>

  <ol>
    <li>Spring 会为你的 <code class="language-plaintext highlighter-rouge">VoucherOrderServiceImpl</code> 创建一个 <strong>代理对象</strong> （JDK 动态代理或 CGLIB 代理）。</li>
    <li>当外部调用 <code class="language-plaintext highlighter-rouge">voucherOrderService.seckillVoucher(...)</code> 时，实际调用的是<strong>代理对象</strong>的方法。</li>
    <li>代理对象在方法执行前后，会织入事务管理的逻辑：开启事务 → 调用目标对象的方法 → 提交/回滚事务。</li>
  </ol>
</blockquote>

<p>但 <strong>如果目标对象内部通过 <code class="language-plaintext highlighter-rouge">this.xxx()</code> 调用自己的另一个方法，这个调用直接发生在原始对象（目标对象）上，根本不会经过代理对象</strong> 。</p>

<p>示意：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>外部调用 → 代理对象（有事务增强）→ 目标对象.seckillVoucher()
                                         ↓ (this.createOrder)
                              目标对象.createOrder()  ← 这里没有代理，事务逻辑完全跳过！
</code></pre></div></div>

<p>因此，<code class="language-plaintext highlighter-rouge">this.createOrder()</code> 就是一个 <strong>普通的 Java 方法调用</strong> ，<code class="language-plaintext highlighter-rouge">@Transactional</code> 注解等于没写。</p>

<blockquote>
  <p>Spring AOP 自调用事务失效问题：</p>

  <ul>
    <li>直接调用本类的事务方法，如 <code class="language-plaintext highlighter-rouge">this.createOrder()</code>，事务注解失效。</li>
  </ul>
</blockquote>

<blockquote>
  <p><strong>原因：</strong> 直接调用时，是目标对象的方法，不会经过代理，事务注解失效。通过代理对象调用时，是代理对象的方法，会经过代理，事务注解生效。</p>
</blockquote>

<p>因此，这段代码的设计逻辑围绕以下要点展开：</p>

<ul>
  <li>使用 <code class="language-plaintext highlighter-rouge">synchronized</code> 对用户 ID 加锁，保证同一用户的请求串行执行。</li>
  <li>通过 <strong>代理对象</strong> 调用事务方法，让 <code class="language-plaintext highlighter-rouge">@Transactional</code> 生效。</li>
  <li>在事务中完成“判断是否已购买 -&gt; 扣库存 -&gt; 创建订单”的原子操作。</li>
</ul>

<hr />

<h2 id="2-代码整体流程">2. 代码整体流程</h2>

<ol>
  <li>校验秒杀优惠券是否存在，是否在有效期内，库存是否大于 0。</li>
  <li>获取当前登录用户 ID，对 <code class="language-plaintext highlighter-rouge">userID.toString().intern()</code> 加锁（保证同一用户串行）。</li>
  <li>通过 <code class="language-plaintext highlighter-rouge">AopContext.currentProxy()</code> 获取当前类的代理对象，调用 <code class="language-plaintext highlighter-rouge">createOrder()</code> 方法。</li>
  <li>在 <code class="language-plaintext highlighter-rouge">createOrder()</code> 中（此时事务已生效）：
    <ul>
      <li>再次校验该用户是否已有订单。</li>
      <li>使用乐观锁思想更新库存：<code class="language-plaintext highlighter-rouge">setSql("stock = stock - 1")...gt("stock", 0)</code>。</li>
      <li>生成全局唯一订单 ID，保存订单信息。</li>
    </ul>
  </li>
  <li>返回订单 ID 或错误提示。</li>
</ol>

<hr />

<h2 id="3-设计要点分析">3. 设计要点分析</h2>

<h3 id="31-为什么使用-aopcontextcurrentproxy">3.1 为什么使用 <code class="language-plaintext highlighter-rouge">AopContext.currentProxy()</code>？</h3>

<p><code class="language-plaintext highlighter-rouge">createOrder()</code> 方法被 <code class="language-plaintext highlighter-rouge">@Transactional</code> 标注，Spring 通过 AOP 生成代理对象来管理事务。<strong>在同一类中直接通过 <code class="language-plaintext highlighter-rouge">this.createOrder()</code> 调用时，调用的是目标对象的方法，不会经过代理，因此事务注解失效。</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">// 错误示例：事务不会生效</span>
<span class="kd">public</span> <span class="nc">Result</span> <span class="nf">seckillVoucher</span><span class="o">(</span><span class="nc">Long</span> <span class="n">voucherId</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">// ...</span>
    <span class="kd">synchronized</span> <span class="o">(</span><span class="n">userID</span><span class="o">.</span><span class="na">toString</span><span class="o">().</span><span class="na">intern</span><span class="o">())</span> <span class="o">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">createOrder</span><span class="o">(</span><span class="n">voucherId</span><span class="o">);</span> <span class="c1">// 直接调用，无事务</span>
    <span class="o">}</span>
<span class="o">}</span>

</code></pre></div></div>

<p>正确做法是获取当前的 AOP 代理，用代理对象调用：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">IVoucherOrderService</span> <span class="n">proxy</span> <span class="o">=</span> <span class="o">(</span><span class="nc">IVoucherOrderService</span><span class="o">)</span> <span class="nc">AopContext</span><span class="o">.</span><span class="na">currentProxy</span><span class="o">();</span>
<span class="k">return</span> <span class="n">proxy</span><span class="o">.</span><span class="na">createOrder</span><span class="o">(</span><span class="n">voucherId</span><span class="o">);</span>
</code></pre></div></div>

<p><strong>前提</strong>：需要在启动类或配置类上添加 <code class="language-plaintext highlighter-rouge">@EnableAspectJAutoProxy(exposeProxy = true)</code> 暴露代理对象，否则会抛出异常。</p>

<h3 id="32-事务设计原因">3.2 事务设计原因</h3>

<p><code class="language-plaintext highlighter-rouge">createOrder()</code> 内的三个操作必须在一个事务中完成：</p>

<ol>
  <li><strong>查询该用户是否已有此优惠券的订单</strong>。</li>
  <li><strong>扣减库存</strong>（采用 <code class="language-plaintext highlighter-rouge">stock &gt; 0</code> 条件防止超卖）。</li>
  <li><strong>保存订单</strong>。</li>
</ol>

<p>任何一步失败，整个事务回滚，保证数据一致性。例如：扣库存成功后，保存订单时出现异常，库存会回滚，不会出现“扣了库存却没生成订单”的情况。</p>

<h3 id="33-锁的设计">3.3 锁的设计</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">synchronized</span> <span class="o">(</span><span class="n">userID</span><span class="o">.</span><span class="na">toString</span><span class="o">().</span><span class="na">intern</span><span class="o">())</span> <span class="o">{</span>
    <span class="c1">// ...</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>为什么锁对象是 <code class="language-plaintext highlighter-rouge">userID.toString().intern()</code>？</strong></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">userID</code> 是 <code class="language-plaintext highlighter-rouge">Long</code> 类型，直接 <code class="language-plaintext highlighter-rouge">synchronized (userID)</code> 会因为自动装箱生成不同对象，导致锁失效。</li>
  <li><code class="language-plaintext highlighter-rouge">userID.toString()</code> 每次生成新的字符串对象，也不能作为锁。</li>
  <li><code class="language-plaintext highlighter-rouge">.intern()</code> 保证同一个内容的字符串在常量池中是同一个对象，<strong>相同的用户 ID 会竞争同一把锁</strong>。</li>
  <li>锁的粒度是“用户级”，不同用户之间不互相阻塞，兼顾并发性能与安全性。</li>
</ul>

<p><strong>为什么在锁块内还要再查一次订单？</strong></p>

<p>外层的库存和秒杀时间判断是无锁的，多个线程可能同时通过前置检查进入锁等待。第一个线程创建订单后，后续等待的线程再执行业务时，必须再次检查该用户是否已购买，否则可能产生重复订单。这一层二次检查（类似单例模式的双重检查）正是锁存在的意义。</p>

<hr />

<h2 id="4-小结">4. 小结</h2>

<ul>
  <li><strong>事务</strong>保证了“扣库存”与“创建订单”的原子性。</li>
  <li><strong>AOP 代理</strong>解决了同一类内事务自调用失效的问题。</li>
  <li><strong>对用户 ID 加锁</strong>实现了一人一单且用户间并发的串行化，配合事务内的重复查询防止重复下单。</li>
  <li><strong>库存更新用乐观锁</strong> (<code class="language-plaintext highlighter-rouge">gt("stock", 0)</code>) 进一步避免超卖。</li>
</ul>

<p>整套设计环环相扣，是学习 Spring 事务和并发控制的经典案例。</p>

<hr />

<h2 id="5-源码附上">5. 源码附上</h2>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.hmdp.service.impl</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">com.hmdp.dto.Result</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.entity.SeckillVoucher</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.entity.VoucherOrder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.mapper.VoucherOrderMapper</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.service.ISeckillVoucherService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.service.IVoucherOrderService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.extension.service.impl.ServiceImpl</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.utils.RedisIdWorker</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.hmdp.utils.UserHolder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.aop.framework.AopContext</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.data.redis.core.StringRedisTemplate</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.stereotype.Service</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.transaction.annotation.Transactional</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">javax.annotation.Resource</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.time.LocalDateTime</span><span class="o">;</span>

<span class="nd">@Service</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">VoucherOrderServiceImpl</span>
        <span class="kd">extends</span> <span class="nc">ServiceImpl</span><span class="o">&lt;</span><span class="nc">VoucherOrderMapper</span><span class="o">,</span> <span class="nc">VoucherOrder</span><span class="o">&gt;</span>
        <span class="kd">implements</span> <span class="nc">IVoucherOrderService</span> <span class="o">{</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">StringRedisTemplate</span> <span class="n">stringRedisTemplate</span><span class="o">;</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">RedisIdWorker</span> <span class="n">redisIdWorker</span><span class="o">;</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="nc">ISeckillVoucherService</span> <span class="n">seckillVoucherService</span><span class="o">;</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">Result</span> <span class="nf">seckillVoucher</span><span class="o">(</span><span class="nc">Long</span> <span class="n">voucherId</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// 1. 查询秒杀优惠券</span>
        <span class="nc">SeckillVoucher</span> <span class="n">seckillVoucher</span> <span class="o">=</span> <span class="n">seckillVoucherService</span><span class="o">.</span><span class="na">getById</span><span class="o">(</span><span class="n">voucherId</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">seckillVoucher</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"优惠券不存在"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="nc">Integer</span> <span class="n">stock</span> <span class="o">=</span> <span class="n">seckillVoucher</span><span class="o">.</span><span class="na">getStock</span><span class="o">();</span>
        <span class="nc">LocalDateTime</span> <span class="n">startTime</span> <span class="o">=</span> <span class="n">seckillVoucher</span><span class="o">.</span><span class="na">getBeginTime</span><span class="o">();</span>
        <span class="nc">LocalDateTime</span> <span class="n">endTime</span> <span class="o">=</span> <span class="n">seckillVoucher</span><span class="o">.</span><span class="na">getEndTime</span><span class="o">();</span>
        <span class="nc">LocalDateTime</span> <span class="n">now</span> <span class="o">=</span> <span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">();</span>

        <span class="c1">// 2. 判断秒杀是否开始</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">now</span><span class="o">.</span><span class="na">isBefore</span><span class="o">(</span><span class="n">startTime</span><span class="o">))</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"秒杀优惠券未开始"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="c1">// 3. 判断秒杀是否结束</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">now</span><span class="o">.</span><span class="na">isAfter</span><span class="o">(</span><span class="n">endTime</span><span class="o">))</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"秒杀优惠券已结束"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="c1">// 4. 判断库存</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">stock</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"秒杀优惠券已售罄"</span><span class="o">);</span>
        <span class="o">}</span>

        <span class="c1">// 一人一单：对用户ID加锁</span>
        <span class="nc">Long</span> <span class="n">userId</span> <span class="o">=</span> <span class="nc">UserHolder</span><span class="o">.</span><span class="na">getUser</span><span class="o">().</span><span class="na">getId</span><span class="o">();</span>
        <span class="kd">synchronized</span> <span class="o">(</span><span class="n">userId</span><span class="o">.</span><span class="na">toString</span><span class="o">().</span><span class="na">intern</span><span class="o">())</span> <span class="o">{</span>
            <span class="c1">// 获取代理对象，让事务生效</span>
            <span class="nc">IVoucherOrderService</span> <span class="n">proxy</span> <span class="o">=</span>
                    <span class="o">(</span><span class="nc">IVoucherOrderService</span><span class="o">)</span> <span class="nc">AopContext</span><span class="o">.</span><span class="na">currentProxy</span><span class="o">();</span>
            <span class="k">return</span> <span class="n">proxy</span><span class="o">.</span><span class="na">createOrder</span><span class="o">(</span><span class="n">voucherId</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="nd">@Transactional</span>
    <span class="kd">public</span> <span class="nc">Result</span> <span class="nf">createOrder</span><span class="o">(</span><span class="nc">Long</span> <span class="n">voucherId</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Long</span> <span class="n">userId</span> <span class="o">=</span> <span class="nc">UserHolder</span><span class="o">.</span><span class="na">getUser</span><span class="o">().</span><span class="na">getId</span><span class="o">();</span>

        <span class="c1">// 双重检查：防止锁内重复下单</span>
        <span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="n">query</span><span class="o">()</span>
                <span class="o">.</span><span class="na">eq</span><span class="o">(</span><span class="s">"user_id"</span><span class="o">,</span> <span class="n">userId</span><span class="o">)</span>
                <span class="o">.</span><span class="na">eq</span><span class="o">(</span><span class="s">"voucher_id"</span><span class="o">,</span> <span class="n">voucherId</span><span class="o">)</span>
                <span class="o">.</span><span class="na">count</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">count</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"用户已购买过该优惠券"</span><span class="o">);</span>
        <span class="o">}</span>

        <span class="c1">// 5. 扣减库存（乐观锁）</span>
        <span class="kt">boolean</span> <span class="n">result</span> <span class="o">=</span> <span class="n">seckillVoucherService</span><span class="o">.</span><span class="na">update</span><span class="o">()</span>
                <span class="o">.</span><span class="na">setSql</span><span class="o">(</span><span class="s">"stock = stock - 1"</span><span class="o">)</span>
                <span class="o">.</span><span class="na">eq</span><span class="o">(</span><span class="s">"voucher_id"</span><span class="o">,</span> <span class="n">voucherId</span><span class="o">)</span>
                <span class="o">.</span><span class="na">gt</span><span class="o">(</span><span class="s">"stock"</span><span class="o">,</span> <span class="mi">0</span><span class="o">)</span>
                <span class="o">.</span><span class="na">update</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">result</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">fail</span><span class="o">(</span><span class="s">"秒杀优惠券已售罄"</span><span class="o">);</span>
        <span class="o">}</span>

        <span class="c1">// 6. 创建订单</span>
        <span class="nc">Long</span> <span class="n">orderId</span> <span class="o">=</span> <span class="n">redisIdWorker</span><span class="o">.</span><span class="na">nextId</span><span class="o">(</span><span class="s">"order"</span><span class="o">);</span>
        <span class="nc">VoucherOrder</span> <span class="n">voucherOrder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">VoucherOrder</span><span class="o">();</span>
        <span class="n">voucherOrder</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">orderId</span><span class="o">);</span>
        <span class="n">voucherOrder</span><span class="o">.</span><span class="na">setUserId</span><span class="o">(</span><span class="n">userId</span><span class="o">);</span>
        <span class="n">voucherOrder</span><span class="o">.</span><span class="na">setVoucherId</span><span class="o">(</span><span class="n">voucherId</span><span class="o">);</span>
        <span class="n">save</span><span class="o">(</span><span class="n">voucherOrder</span><span class="o">);</span>

        <span class="k">return</span> <span class="nc">Result</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">orderId</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<hr />

<blockquote>
  <p><strong>提示</strong>：使用该代码时，请确保配置类上添加了 <code class="language-plaintext highlighter-rouge">@EnableAspectJAutoProxy(exposeProxy = true)</code>，并且 <code class="language-plaintext highlighter-rouge">UserHolder</code> 能正确获取当前线程的用户信息。</p>
</blockquote>

<hr />

<p>希望这篇博客能帮助你理解 Spring 中事务、代理与锁协同工作的精妙之处。如有疑问，欢迎交流讨论！</p>]]></content><author><name>溄</name><email>hongqiang.yan@qq.com</email></author><category term="学习笔记" /><category term="Redis" /><category term="黑马课程" /><category term="学习笔记" /><category term="Java" /><category term="Spring" /><category term="事务" /><category term="锁" /><summary type="html"><![CDATA[Redis 黑马课程学习：秒杀优惠券一人一单——Spring 事务与锁的巧妙配合。]]></summary></entry><entry><title type="html">华为昇腾CANN算子进阶开发：Tiling模板、属性、TBuf与Workspace全解析</title><link href="https://yhongq.github.io/posts/cann-tiling-tbuf-workspace/" rel="alternate" type="text/html" title="华为昇腾CANN算子进阶开发：Tiling模板、属性、TBuf与Workspace全解析" /><published>2026-05-25T00:00:00+08:00</published><updated>2026-05-25T00:00:00+08:00</updated><id>https://yhongq.github.io/posts/cann-tiling-tbuf-workspace</id><content type="html" xml:base="https://yhongq.github.io/posts/cann-tiling-tbuf-workspace/"><![CDATA[<h1 id="华为昇腾cann算子进阶开发tiling模板属性tbuf与workspace全解析">华为昇腾CANN算子进阶开发：Tiling模板、属性、TBuf与Workspace全解析</h1>

<blockquote>
  <p>本文深入讲解华为昇腾CANN算子开发中的高级特性：Tiling模板编程、算子属性、TBuf临时存储和Workspace工作空间。从C++模板基础到内存管理机制，从Host侧配置到Device侧实现，带你全面掌握昇腾AI芯片的算子开发进阶技巧。</p>
</blockquote>

<h2 id="1-背景与目标">1. 背景与目标</h2>

<p>在掌握了基础算子开发后，实际生产环境中还需要处理更复杂的场景：支持多种数据类型、需要额外的临时存储空间、算子需要配置参数等。本文的目标是：</p>

<ul>
  <li>理解C++模板编程在算子开发中的应用</li>
  <li>掌握Workspace和TBuf的内存管理机制</li>
  <li>学习算子属性的定义和使用方法</li>
  <li>深入理解Tiling模板编程的优势和实现</li>
  <li>掌握Host侧和Device侧的内存位置关系</li>
  <li>实现一个完整的Clamp算子，综合运用所学知识</li>
</ul>

<h2 id="2-c模板编程基础">2. C++模板编程基础</h2>

<p>在深入昇腾CANN的Tiling模板编程之前，我们需要先理解C++模板编程的基础知识。</p>

<h3 id="21-什么是模板">2.1 什么是模板</h3>

<p>模板是C++的泛型编程机制，允许我们编写与类型无关的代码。编译器会根据具体的使用类型生成对应的代码。</p>

<h4 id="211-函数模板">2.1.1 函数模板</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 传统的写法 - 需要为每种类型写一个函数</span>
<span class="kt">int</span> <span class="nf">add_int</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">float</span> <span class="n">add_float</span><span class="p">(</span><span class="kt">float</span> <span class="n">a</span><span class="p">,</span> <span class="kt">float</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// 使用模板 - 一个函数支持所有类型</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="n">T</span> <span class="n">add</span><span class="p">(</span><span class="n">T</span> <span class="n">a</span><span class="p">,</span> <span class="n">T</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// 使用方式</span>
<span class="kt">int</span> <span class="n">result1</span> <span class="o">=</span> <span class="n">add</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>        <span class="c1">// 显式指定类型</span>
<span class="kt">float</span> <span class="n">result2</span> <span class="o">=</span> <span class="n">add</span><span class="p">(</span><span class="mf">2.5</span><span class="n">f</span><span class="p">,</span> <span class="mf">3.7</span><span class="n">f</span><span class="p">);</span>     <span class="c1">// 自动类型推导</span>
</code></pre></div></div>

<p><strong>模板的优势：</strong></p>

<ol>
  <li><strong>代码复用</strong>：避免为每种类型重复编写相同逻辑</li>
  <li><strong>类型安全</strong>：编译时进行类型检查</li>
  <li><strong>性能优化</strong>：编译器针对具体类型生成优化代码</li>
</ol>

<h4 id="212-类模板">2.1.2 类模板</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 类模板示例</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">Container</span> <span class="p">{</span>
<span class="nl">private:</span>
    <span class="n">T</span> <span class="n">data</span><span class="p">;</span>
<span class="nl">public:</span>
    <span class="n">Container</span><span class="p">(</span><span class="n">T</span> <span class="n">value</span><span class="p">)</span> <span class="o">:</span> <span class="n">data</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{}</span>
    <span class="n">T</span> <span class="n">getValue</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">data</span><span class="p">;</span> <span class="p">}</span>
    <span class="kt">void</span> <span class="n">setValue</span><span class="p">(</span><span class="n">T</span> <span class="n">value</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span> <span class="o">=</span> <span class="n">value</span><span class="p">;</span> <span class="p">}</span>
<span class="p">};</span>

<span class="c1">// 使用方式</span>
<span class="n">Container</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">intContainer</span><span class="p">(</span><span class="mi">42</span><span class="p">);</span>
<span class="n">Container</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">floatContainer</span><span class="p">(</span><span class="mf">3.14</span><span class="n">f</span><span class="p">);</span>
</code></pre></div></div>

<h4 id="213-模板特化">2.1.3 模板特化</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 通用模板</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">TypeInfo</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="k">static</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">getName</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s">"Unknown Type"</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="c1">// 特化版本 - 针对int类型</span>
<span class="k">template</span><span class="o">&lt;</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">TypeInfo</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="k">static</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">getName</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s">"int"</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="c1">// 特化版本 - 针对float类型</span>
<span class="k">template</span><span class="o">&lt;</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">TypeInfo</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="k">static</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">getName</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s">"float"</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="22-模板在昇腾算子开发中的应用">2.2 模板在昇腾算子开发中的应用</h3>

<p>在昇腾算子开发中，模板主要用于：</p>

<ol>
  <li><strong>支持多种数据类型</strong>：一个算子支持float16、float32、int32等多种类型</li>
  <li><strong>编译时优化</strong>：根据具体类型生成最优代码</li>
  <li><strong>代码维护</strong>：避免为每种类型维护单独的代码</li>
</ol>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 昇腾算子中的模板应用示例</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">dtypeX</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">dtypeY</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">dtypeZ</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">KernelAdd</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Init</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">,</span> <span class="p">...)</span> <span class="p">{</span>
        <span class="c1">// 根据具体类型初始化</span>
        <span class="n">xGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">dtypeX</span> <span class="o">*</span><span class="p">)</span><span class="n">x</span><span class="p">,</span> <span class="n">totalLength</span><span class="p">);</span>
        <span class="n">yGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">dtypeY</span> <span class="o">*</span><span class="p">)</span><span class="n">y</span><span class="p">,</span> <span class="n">totalLength</span><span class="p">);</span>
        <span class="n">zGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">dtypeZ</span> <span class="o">*</span><span class="p">)</span><span class="n">z</span><span class="p">,</span> <span class="n">totalLength</span><span class="p">);</span>
    <span class="p">}</span>
  
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Compute</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">inQueueY</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="n">outQueueZ</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span><span class="p">();</span>
      
        <span class="c1">// 使用模板类型进行计算</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">Add</span><span class="p">(</span><span class="n">zLocal</span><span class="p">,</span> <span class="n">xLocal</span><span class="p">,</span> <span class="n">yLocal</span><span class="p">,</span> <span class="n">tileLength</span><span class="p">);</span>
      
        <span class="n">outQueueZ</span><span class="p">.</span><span class="n">EnQue</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>
        <span class="n">inQueueX</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
        <span class="n">inQueueY</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="3-pytorch算子属性介绍">3. PyTorch算子属性介绍</h2>

<p>在深入昇腾CANN的算子属性之前，我们先了解PyTorch中算子属性的概念，这有助于我们理解属性的本质和作用。</p>

<h3 id="31-什么是算子属性">3.1 什么是算子属性</h3>

<p>算子属性是算子在定义阶段就确定的<strong>静态参数</strong>，在计算过程中保持不变。它与动态变化的输入张量相互独立，用于约束和控制算子的计算行为。</p>

<h3 id="32-pytorch中的属性示例">3.2 PyTorch中的属性示例</h3>

<h4 id="321-必选属性">3.2.1 必选属性</h4>

<p>必选属性是算子运行的核心前提，缺少这些属性算子无法完成初始化或执行。</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">torch</span>
<span class="kn">import</span> <span class="nn">torch.nn</span> <span class="k">as</span> <span class="n">nn</span>

<span class="c1"># Conv2d的必选属性
</span><span class="n">conv</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Conv2d</span><span class="p">(</span>
    <span class="n">in_channels</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span>      <span class="c1"># 必选：输入通道数
</span>    <span class="n">out_channels</span><span class="o">=</span><span class="mi">64</span><span class="p">,</span>    <span class="c1"># 必选：输出通道数
</span>    <span class="n">kernel_size</span><span class="o">=</span><span class="mi">3</span>       <span class="c1"># 必选：卷积核尺寸
</span><span class="p">)</span>

<span class="c1"># 缺少这些属性，卷积层无法确定权重矩阵形状和计算逻辑
</span></code></pre></div></div>

<p><strong>必选属性的作用：</strong></p>

<ul>
  <li>决定算子的核心功能</li>
  <li>确定计算所需的资源分配</li>
  <li>定义算子的输入输出关系</li>
</ul>

<h4 id="322-可选属性">3.2.2 可选属性</h4>

<p>可选属性是算子的辅助配置，未指定时使用默认值，不改变算子的核心功能。</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Conv2d的可选属性
</span><span class="n">conv</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Conv2d</span><span class="p">(</span>
    <span class="n">in_channels</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span>
    <span class="n">out_channels</span><span class="o">=</span><span class="mi">64</span><span class="p">,</span>
    <span class="n">kernel_size</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span>
    <span class="n">stride</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>           <span class="c1"># 可选：步长，默认为1
</span>    <span class="n">padding</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>          <span class="c1"># 可选：填充，默认为0
</span>    <span class="n">bias</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>          <span class="c1"># 可选：是否使用偏置，默认为True
</span>    <span class="n">dilation</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>         <span class="c1"># 可选：膨胀率，默认为1
</span>    <span class="n">groups</span><span class="o">=</span><span class="mi">1</span>            <span class="c1"># 可选：分组数，默认为1
</span><span class="p">)</span>

<span class="c1"># 这些属性只调整计算细节，不改变"执行卷积运算"的核心事实
</span></code></pre></div></div>

<p><strong>可选属性的作用：</strong></p>

<ul>
  <li>控制算子的计算细节</li>
  <li>调整输出特征图尺寸</li>
  <li>优化计算性能或精度</li>
</ul>

<h3 id="33-属性的本质特征">3.3 属性的本质特征</h3>

<p>无论是PyTorch还是昇腾CANN，算子属性都具有以下特征：</p>

<table>
  <thead>
    <tr>
      <th>特征</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>静态性</strong></td>
      <td>运行过程中不可修改，需在调用算子前确定</td>
    </tr>
    <tr>
      <td><strong>解耦性</strong></td>
      <td>与输入张量解耦，不依赖数据的形状/数值</td>
    </tr>
    <tr>
      <td><strong>分类性</strong></td>
      <td>通过”必选/可选”划分，必选决定核心功能，可选定制细节</td>
    </tr>
  </tbody>
</table>

<h2 id="4-workspace设备侧工作空间">4. Workspace：设备侧工作空间</h2>

<h3 id="41-什么是workspace">4.1 什么是Workspace</h3>

<p>Workspace是设备侧（Device侧）Global Memory上的一块内存区域，用于存储算子计算过程中需要的临时数据。它分为两部分：</p>

<h4 id="411-系统workspace">4.1.1 系统Workspace</h4>

<p>系统Workspace是Ascend C API需要预留的内存空间。某些API在计算过程中需要额外的内存作为缓存，因此算子需要为这些API预留Workspace内存。</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 获取系统Workspace大小</span>
<span class="k">auto</span> <span class="n">ascendcPlatform</span> <span class="o">=</span> <span class="n">platform_ascendc</span><span class="o">::</span><span class="n">PlatformAscendC</span><span class="p">(</span><span class="n">context</span><span class="o">-&gt;</span><span class="n">GetPlatformInfo</span><span class="p">());</span>
<span class="kt">uint32_t</span> <span class="n">sysWorkspaceSize</span> <span class="o">=</span> <span class="n">ascendcPlatform</span><span class="p">.</span><span class="n">GetLibApiWorkSpaceSize</span><span class="p">();</span>
</code></pre></div></div>

<h4 id="412-用户workspace">4.1.2 用户Workspace</h4>

<p>用户Workspace是算子实现过程中自行分配和使用的内存空间。使用场景包括：</p>

<ul>
  <li><strong>数据暂存</strong>：当Unified Buffer和L1 Buffer空间不足时，将数据暂存至Workspace</li>
  <li><strong>API调用</strong>：某些API（如SyncAll）需要Workspace作为入参</li>
  <li><strong>中间结果</strong>：存储计算过程中的中间结果</li>
</ul>

<h3 id="42-workspace的内存位置">4.2 Workspace的内存位置</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────────┐
│                    Host侧 (CPU)                          │
│  ┌─────────────────────────────────────────────────┐   │
│  │         Host Memory (系统内存)                   │   │
│  │  - 输入数据                                       │   │
│  │  - 输出数据                                       │   │
│  │  - 模型参数                                       │   │
│  └─────────────────────────────────────────────────┘   │
└────────────────────┬────────────────────────────────────┘
                     │ 数据搬运 (aclrtMemcpy)
┌────────────────────▼────────────────────────────────────┐
│                  Device侧 (AI芯片)                       │
│  ┌─────────────────────────────────────────────────┐   │
│  │         Global Memory (HBM)                     │   │
│  │  ┌─────────────────────────────────────────┐   │   │
│  │  │  输入/输出张量                            │   │   │
│  │  └─────────────────────────────────────────┘   │   │
│  │  ┌─────────────────────────────────────────┐   │   │
│  │  │  Workspace (工作空间)                    │   │   │
│  │  │  - 系统Workspace (API预留)               │   │   │
│  │  │  - 用户Workspace (算子使用)               │   │   │
│  │  └─────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────┐   │
│  │         LocalMemory (片上存储)                  │   │
│  │  - Unified Buffer (UB)                          │   │
│  │  - L1 Buffer                                    │   │
│  │  - L0 Buffer                                    │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘
</code></pre></div></div>

<h3 id="43-host侧workspace配置">4.3 Host侧Workspace配置</h3>

<p>在Host侧的Tiling函数中配置Workspace大小：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="nf">TilingFunc</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">TilingContext</span><span class="o">*</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 获取平台信息</span>
    <span class="k">auto</span> <span class="n">ascendcPlatform</span> <span class="o">=</span> <span class="n">platform_ascendc</span><span class="o">::</span><span class="n">PlatformAscendC</span><span class="p">(</span><span class="n">context</span><span class="o">-&gt;</span><span class="n">GetPlatformInfo</span><span class="p">());</span>
  
    <span class="c1">// 获取系统Workspace大小</span>
    <span class="kt">uint32_t</span> <span class="n">sysWorkspaceSize</span> <span class="o">=</span> <span class="n">ascendcPlatform</span><span class="p">.</span><span class="n">GetLibApiWorkSpaceSize</span><span class="p">();</span>
  
    <span class="c1">// 设置用户Workspace大小（根据实际需求）</span>
    <span class="kt">size_t</span> <span class="n">userWorkspaceSize</span> <span class="o">=</span> <span class="mi">256</span> <span class="o">*</span> <span class="mi">4</span><span class="p">;</span>  <span class="c1">// 256个元素，每个4字节</span>
  
    <span class="c1">// 获取Workspace指针并设置总大小</span>
    <span class="kt">size_t</span> <span class="o">*</span><span class="n">currentWorkspace</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetWorkspaceSizes</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
    <span class="n">currentWorkspace</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">userWorkspaceSize</span> <span class="o">+</span> <span class="n">sysWorkspaceSize</span><span class="p">;</span>
  
    <span class="k">return</span> <span class="n">ge</span><span class="o">::</span><span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>关键步骤：</strong></p>

<ol>
  <li><strong>获取平台信息</strong>：通过 <code class="language-plaintext highlighter-rouge">PlatformAscendC</code>获取硬件平台信息</li>
  <li><strong>计算系统Workspace</strong>：调用 <code class="language-plaintext highlighter-rouge">GetLibApiWorkSpaceSize()</code>获取API需要的空间</li>
  <li><strong>计算用户Workspace</strong>：根据算子实际需求确定大小</li>
  <li><strong>设置总大小</strong>：系统Workspace + 用户Workspace</li>
</ol>

<h3 id="44-device侧workspace使用">4.4 Device侧Workspace使用</h3>

<p>在Device侧的核函数中使用Workspace：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">dtypeX</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">dtypeY</span><span class="p">&gt;</span>
<span class="n">__global__</span> <span class="n">__aicore__</span> <span class="kt">void</span> <span class="nf">clamp</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">workspace</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">tiling</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 将Workspace作为GlobalTensor使用</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">tmpGm</span><span class="p">;</span>
    <span class="n">tmpGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="kt">float</span> <span class="o">*</span><span class="p">)</span><span class="n">workspace</span><span class="p">);</span>
  
    <span class="c1">// 可以像使用普通GlobalTensor一样使用Workspace</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">tmpLocal</span> <span class="o">=</span> <span class="n">tmpGm</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
  
    <span class="c1">// 使用Workspace进行计算</span>
    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>使用注意事项：</strong></p>

<ul>
  <li>Workspace位于Global Memory，访问速度比LocalMemory慢</li>
  <li>使用Workspace需要额外的数据搬运操作</li>
  <li>合理规划Workspace大小，避免浪费内存</li>
</ul>

<h2 id="5-tbuflocalmemory临时存储">5. TBuf：LocalMemory临时存储</h2>

<h3 id="51-tbuf的作用">5.1 TBuf的作用</h3>

<p>TBuf是位于LocalMemory（片上存储）的临时存储空间，主要用于Vector计算过程中的数据暂存。与Workspace相比，TBuf具有以下优势：</p>

<table>
  <thead>
    <tr>
      <th>特性</th>
      <th>Workspace</th>
      <th>TBuf</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>内存位置</strong></td>
      <td>Global Memory (HBM)</td>
      <td>LocalMemory (片上)</td>
    </tr>
    <tr>
      <td><strong>访问速度</strong></td>
      <td>较慢</td>
      <td>快速</td>
    </tr>
    <tr>
      <td><strong>数据搬运</strong></td>
      <td>需要搬运操作</td>
      <td>无需搬运</td>
    </tr>
    <tr>
      <td><strong>使用场景</strong></td>
      <td>大量数据暂存</td>
      <td>计算过程临时存储</td>
    </tr>
  </tbody>
</table>

<h3 id="52-tbuf-vs-tque">5.2 TBuf vs TQue</h3>

<p>TBuf和TQue都用于LocalMemory的内存管理，但使用方式有所不同：</p>

<h4 id="521-相同点">5.2.1 相同点</h4>

<ul>
  <li>都通过 <code class="language-plaintext highlighter-rouge">InitBuffer</code>接口初始化内存</li>
  <li>都由TPipe统一管理</li>
  <li>都位于LocalMemory中</li>
</ul>

<h4 id="522-不同点">5.2.2 不同点</h4>

<table>
  <thead>
    <tr>
      <th>特性</th>
      <th>TBuf</th>
      <th>TQue</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>获取内存</strong></td>
      <td>通过 <code class="language-plaintext highlighter-rouge">Get()</code></td>
      <td>通过 <code class="language-plaintext highlighter-rouge">AllocTensor()</code></td>
    </tr>
    <tr>
      <td><strong>队列操作</strong></td>
      <td>不支持</td>
      <td>支持 <code class="language-plaintext highlighter-rouge">EnQue</code>/<code class="language-plaintext highlighter-rouge">DeQue</code></td>
    </tr>
    <tr>
      <td><strong>内存释放</strong></td>
      <td>无需释放</td>
      <td>需要 <code class="language-plaintext highlighter-rouge">FreeTensor()</code></td>
    </tr>
    <tr>
      <td><strong>使用场景</strong></td>
      <td>计算过程临时存储</td>
      <td>流水线数据传递</td>
    </tr>
  </tbody>
</table>

<h3 id="53-tbuf的使用示例">5.3 TBuf的使用示例</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">KernelAdd</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="n">KernelAdd</span><span class="p">()</span> <span class="p">{}</span>
  
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Init</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 初始化GlobalTensor</span>
        <span class="n">xGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">half</span> <span class="o">*</span><span class="p">)</span><span class="n">x</span><span class="p">,</span> <span class="n">TOTAL_LENGTH</span><span class="p">);</span>
        <span class="n">yGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">half</span> <span class="o">*</span><span class="p">)</span><span class="n">y</span><span class="p">,</span> <span class="n">TOTAL_LENGTH</span><span class="p">);</span>
        <span class="n">zGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">half</span> <span class="o">*</span><span class="p">)</span><span class="n">z</span><span class="p">,</span> <span class="n">TOTAL_LENGTH</span><span class="p">);</span>

        <span class="c1">// 初始化队列</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">inQueueX</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">TOTAL_LENGTH</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">bfloat16_t</span><span class="p">));</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">inQueueY</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">TOTAL_LENGTH</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">bfloat16_t</span><span class="p">));</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">outQueueZ</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">TOTAL_LENGTH</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">bfloat16_t</span><span class="p">));</span>
      
        <span class="c1">// 初始化TBuf（用于类型转换的临时存储）</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">tmpBuf0</span><span class="p">,</span> <span class="n">TOTAL_LENGTH</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">));</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">tmpBuf1</span><span class="p">,</span> <span class="n">TOTAL_LENGTH</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">));</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">tmpBuf2</span><span class="p">,</span> <span class="n">TOTAL_LENGTH</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">));</span>
    <span class="p">}</span>
  
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Compute</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">// 从队列获取输入</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">bfloat16_t</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">bfloat16_t</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">bfloat16_t</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">inQueueY</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">bfloat16_t</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">bfloat16_t</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="n">outQueueZ</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">bfloat16_t</span><span class="o">&gt;</span><span class="p">();</span>
      
        <span class="c1">// 获取TBuf的临时存储空间</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">tmpTensor0</span> <span class="o">=</span> <span class="n">tmpBuf0</span><span class="p">.</span><span class="n">Get</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">tmpTensor1</span> <span class="o">=</span> <span class="n">tmpBuf1</span><span class="p">.</span><span class="n">Get</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">tmpTensor2</span> <span class="o">=</span> <span class="n">tmpBuf2</span><span class="p">.</span><span class="n">Get</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
      
        <span class="c1">// 类型转换：bfloat16 -&gt; float</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">Cast</span><span class="p">(</span><span class="n">tmpTensor0</span><span class="p">,</span> <span class="n">xLocal</span><span class="p">,</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">RoundMode</span><span class="o">::</span><span class="n">CAST_NONE</span><span class="p">,</span> <span class="n">TOTAL_LENGTH</span><span class="p">);</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">Cast</span><span class="p">(</span><span class="n">tmpTensor1</span><span class="p">,</span> <span class="n">yLocal</span><span class="p">,</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">RoundMode</span><span class="o">::</span><span class="n">CAST_NONE</span><span class="p">,</span> <span class="n">TOTAL_LENGTH</span><span class="p">);</span>
      
        <span class="c1">// 使用float类型进行计算</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">Add</span><span class="p">(</span><span class="n">tmpTensor2</span><span class="p">,</span> <span class="n">tmpTensor0</span><span class="p">,</span> <span class="n">tmpTensor1</span><span class="p">,</span> <span class="n">TOTAL_LENGTH</span><span class="p">);</span>
      
        <span class="c1">// 类型转换：float -&gt; bfloat16</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">Cast</span><span class="p">(</span><span class="n">zLocal</span><span class="p">,</span> <span class="n">tmpTensor2</span><span class="p">,</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">RoundMode</span><span class="o">::</span><span class="n">CAST_RINT</span><span class="p">,</span> <span class="n">TOTAL_LENGTH</span><span class="p">);</span>
      
        <span class="c1">// 将结果放入输出队列</span>
        <span class="n">outQueueZ</span><span class="p">.</span><span class="n">EnQue</span><span class="o">&lt;</span><span class="n">bfloat16_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>
        <span class="n">inQueueX</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
        <span class="n">inQueueY</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
    <span class="p">}</span>

<span class="nl">private:</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TPipe</span> <span class="n">pipe</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECIN</span><span class="p">,</span> <span class="mi">1</span><span class="o">&gt;</span> <span class="n">inQueueX</span><span class="p">,</span> <span class="n">inQueueY</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECOUT</span><span class="p">,</span> <span class="mi">1</span><span class="o">&gt;</span> <span class="n">outQueueZ</span><span class="p">;</span>
  
    <span class="c1">// TBuf类型的成员变量</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TBuf</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECCALC</span><span class="o">&gt;</span> <span class="n">tmpBuf0</span><span class="p">,</span> <span class="n">tmpBuf1</span><span class="p">,</span> <span class="n">tmpBuf2</span><span class="p">;</span>
  
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="n">bfloat16_t</span><span class="o">&gt;</span> <span class="n">xGm</span><span class="p">,</span> <span class="n">yGm</span><span class="p">,</span> <span class="n">zGm</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p><strong>TBuf使用要点：</strong></p>

<ol>
  <li><strong>初始化</strong>：通过 <code class="language-plaintext highlighter-rouge">pipe.InitBuffer()</code>分配内存</li>
  <li><strong>获取</strong>：通过 <code class="language-plaintext highlighter-rouge">Get&lt;T&gt;()</code>获取临时Tensor</li>
  <li><strong>使用</strong>：直接参与计算，无需队列操作</li>
  <li><strong>释放</strong>：无需手动释放，由TPipe统一管理</li>
</ol>

<h3 id="54-tbuf的内存位置">5.4 TBuf的内存位置</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────────┐
│              LocalMemory (片上存储)                       │
│  ┌─────────────────────────────────────────────────┐   │
│  │         Unified Buffer (UB)                     │   │
│  │  ┌─────────────────────────────────────────┐   │   │
│  │  │  TQue (队列管理)                        │   │   │
│  │  │  - inQueueX (输入队列)                   │   │   │
│  │  │  - inQueueY (输入队列)                   │   │   │
│  │  │  - outQueueZ (输出队列)                  │   │   │
│  │  └─────────────────────────────────────────┘   │   │
│  │  ┌─────────────────────────────────────────┐   │   │
│  │  │  TBuf (临时存储)                        │   │   │
│  │  │  - tmpBuf0 (临时存储0)                   │   │   │
│  │  │  - tmpBuf1 (临时存储1)                   │   │   │
│  │  │  - tmpBuf2 (临时存储2)                   │   │   │
│  │  └─────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘
</code></pre></div></div>

<h2 id="6-算子属性静态参数配置">6. 算子属性：静态参数配置</h2>

<h3 id="61-属性的分类">6.1 属性的分类</h3>

<h4 id="611-必选属性">6.1.1 必选属性</h4>

<p>必选属性是算子运行的核心前提，决定算子”要做什么”。</p>

<p><strong>昇腾CANN示例：</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 开发卷积算子时的必选属性</span>
<span class="k">class</span> <span class="nc">Conv2D</span> <span class="o">:</span> <span class="k">public</span> <span class="n">OpDef</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="k">explicit</span> <span class="n">Conv2D</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">)</span> <span class="o">:</span> <span class="n">OpDef</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 必选属性</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Attr</span><span class="p">(</span><span class="s">"in_channels"</span><span class="p">).</span><span class="n">Int</span><span class="p">();</span>      <span class="c1">// 输入通道数</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Attr</span><span class="p">(</span><span class="s">"out_channels"</span><span class="p">).</span><span class="n">Int</span><span class="p">();</span>     <span class="c1">// 输出通道数</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Attr</span><span class="p">(</span><span class="s">"kernel_size"</span><span class="p">).</span><span class="n">Int</span><span class="p">();</span>      <span class="c1">// 卷积核尺寸</span>
    <span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>

<h4 id="612-可选属性">6.1.2 可选属性</h4>

<p>可选属性是算子的辅助配置，控制算子”怎么做”。</p>

<p><strong>昇腾CANN示例：</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 开发GELU算子时的可选属性</span>
<span class="k">class</span> <span class="nc">GELU</span> <span class="o">:</span> <span class="k">public</span> <span class="n">OpDef</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="k">explicit</span> <span class="n">GELU</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">)</span> <span class="o">:</span> <span class="n">OpDef</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 可选属性</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Attr</span><span class="p">(</span><span class="s">"approximate"</span><span class="p">).</span><span class="n">String</span><span class="p">().</span><span class="n">SetDefault</span><span class="p">(</span><span class="s">"none"</span><span class="p">);</span>  <span class="c1">// 近似计算方式</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="c1">// 开发Clamp算子时的可选属性</span>
<span class="k">class</span> <span class="nc">Clamp</span> <span class="o">:</span> <span class="k">public</span> <span class="n">OpDef</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="k">explicit</span> <span class="n">Clamp</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">)</span> <span class="o">:</span> <span class="n">OpDef</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 可选属性</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Attr</span><span class="p">(</span><span class="s">"min"</span><span class="p">).</span><span class="n">Float</span><span class="p">().</span><span class="n">SetDefault</span><span class="p">(</span><span class="mf">0.0</span><span class="n">f</span><span class="p">);</span>   <span class="c1">// 最小值</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Attr</span><span class="p">(</span><span class="s">"max"</span><span class="p">).</span><span class="n">Float</span><span class="p">().</span><span class="n">SetDefault</span><span class="p">(</span><span class="mf">1.0</span><span class="n">f</span><span class="p">);</span>   <span class="c1">// 最大值</span>
    <span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="62-属性的定义">6.2 属性的定义</h3>

<p>在算子原型JSON文件中定义属性：</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[{</span><span class="w">
    </span><span class="nl">"op"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Clamp"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"input_desc"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"x"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"param_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"required"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ND"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ND"</span><span class="p">],</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"int32"</span><span class="p">,</span><span class="w"> </span><span class="s2">"float"</span><span class="p">]</span><span class="w">
    </span><span class="p">}],</span><span class="w">
    </span><span class="nl">"output_desc"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"y"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"param_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"required"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ND"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ND"</span><span class="p">],</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"int32"</span><span class="p">,</span><span class="w"> </span><span class="s2">"float"</span><span class="p">]</span><span class="w">
    </span><span class="p">}],</span><span class="w">
    </span><span class="nl">"attr"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"min"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"float"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"param_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"optional"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"default_value"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
    </span><span class="p">}]</span><span class="w">
</span><span class="p">}]</span><span class="w">
</span></code></pre></div></div>

<p><strong>属性字段说明：</strong></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">name</code>：属性名称</li>
  <li><code class="language-plaintext highlighter-rouge">type</code>：属性类型（int/float/string/bool/list等）</li>
  <li><code class="language-plaintext highlighter-rouge">param_type</code>：参数类型（required/optional）</li>
  <li><code class="language-plaintext highlighter-rouge">default_value</code>：默认值（可选属性）</li>
</ul>

<h3 id="63-host侧属性获取">6.3 Host侧属性获取</h3>

<p>在Host侧的Tiling函数中获取属性值：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="nf">TilingFunc</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">TilingContext</span><span class="o">*</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 获取属性值</span>
    <span class="kt">float</span> <span class="n">min_value</span> <span class="o">=</span> <span class="o">*</span><span class="n">context</span><span class="o">-&gt;</span><span class="n">GetAttrs</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetFloat</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
  
    <span class="c1">// 将属性值传递给Tiling数据结构</span>
    <span class="n">ClampTilingData</span> <span class="o">*</span><span class="n">tiling</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetTilingData</span><span class="o">&lt;</span><span class="n">ClampTilingData</span><span class="o">&gt;</span><span class="p">();</span>
    <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">min</span> <span class="o">=</span> <span class="n">min_value</span><span class="p">;</span>
  
    <span class="k">return</span> <span class="n">ge</span><span class="o">::</span><span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="64-device侧属性使用">6.4 Device侧属性使用</h3>

<p>在Device侧的核函数中通过TilingData使用属性：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">dtypeX</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">dtypeY</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">KernelClamp</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Init</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">workspace</span><span class="p">,</span> 
                                <span class="kt">uint32_t</span> <span class="n">totalLength</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">,</span> <span class="kt">float</span> <span class="n">min</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">min</span> <span class="o">=</span> <span class="n">min</span><span class="p">;</span>  <span class="c1">// 保存属性值</span>
        <span class="c1">// 其他初始化...</span>
    <span class="p">}</span>
  
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Compute</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">// 使用属性值进行计算</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">Mins</span><span class="p">(</span><span class="n">yLocal</span><span class="p">,</span> <span class="n">xLocal</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">min</span><span class="p">,</span> <span class="n">tileLength</span><span class="p">);</span>
    <span class="p">}</span>

<span class="nl">private:</span>
    <span class="kt">float</span> <span class="n">min</span><span class="p">;</span>  <span class="c1">// 存储属性值</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="7-tiling模板编程">7. Tiling模板编程</h2>

<h3 id="71-什么是tilingkey">7.1 什么是TilingKey</h3>

<p>TilingKey是一个算子内用于区分不同实现的方法，类似于C++的模板机制。编译时会根据不同的TilingKey生成不同的二进制算子文件，有助于优化单次调用kernel的性能。</p>

<h4 id="711-传统tilingkey方式">7.1.1 传统TilingKey方式</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Host侧设置TilingKey</span>
<span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="nf">TilingFunc</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">TilingContext</span><span class="o">*</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">condition</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">context</span><span class="o">-&gt;</span><span class="n">SetTilingKey</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>  <span class="c1">// 设置TilingKey为1</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">context</span><span class="o">-&gt;</span><span class="n">SetTilingKey</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>  <span class="c1">// 设置TilingKey为2</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">ge</span><span class="o">::</span><span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Device侧根据TilingKey选择分支</span>
<span class="k">extern</span> <span class="s">"C"</span> <span class="n">__global__</span> <span class="n">__aicore__</span> <span class="kt">void</span> <span class="n">add_custom</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">,</span> 
                                                 <span class="n">GM_ADDR</span> <span class="n">workspace</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">tiling</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">TILING_KEY_IS</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">ProcessA</span><span class="p">();</span>  <span class="c1">// 执行分支A</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">TILING_KEY_IS</span><span class="p">(</span><span class="mi">2</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">ProcessB</span><span class="p">();</span>  <span class="c1">// 执行分支B</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>传统方式的缺点：</strong></p>

<ul>
  <li>TilingKey是数字，不易记忆和理解</li>
  <li>在多个TilingKey场景中管理复杂</li>
  <li>代码可读性差</li>
</ul>

<h3 id="72-tiling模板编程">7.2 Tiling模板编程</h3>

<p>Tiling模板编程使用模板参数替代数字TilingKey，使代码更直观和易维护。</p>

<h4 id="721-模板参数定义">7.2.1 模板参数定义</h4>

<p>创建模板定义文件（如 <code class="language-plaintext highlighter-rouge">tiling_key_clamp.h</code>）：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"ascendc/host_api/tiling/template_argument.h"</span><span class="cp">
</span>
<span class="n">ASCENDC_TPL_ARGS_DECL</span><span class="p">(</span><span class="n">Clamp</span><span class="p">,</span>
    <span class="c1">// 数据类型模板参数</span>
    <span class="n">ASCENDC_TPL_DATATYPE_DECL</span><span class="p">(</span><span class="n">D_T_X</span><span class="p">,</span> <span class="n">C_DT_FLOAT</span><span class="p">,</span> <span class="n">C_DT_FLOAT16</span><span class="p">,</span> <span class="n">ASCENDC_TPL_INPUT</span><span class="p">(</span><span class="mi">0</span><span class="p">)),</span>
    <span class="n">ASCENDC_TPL_DATATYPE_DECL</span><span class="p">(</span><span class="n">D_T_Y</span><span class="p">,</span> <span class="n">C_DT_FLOAT</span><span class="p">,</span> <span class="n">C_DT_FLOAT16</span><span class="p">,</span> <span class="n">ASCENDC_TPL_OUTPUT</span><span class="p">(</span><span class="mi">0</span><span class="p">)),</span>
  
    <span class="c1">// 自定义UINT类型模板参数</span>
    <span class="n">ASCENDC_TPL_UINT_DECL</span><span class="p">(</span><span class="n">TILE_NUM</span><span class="p">,</span> <span class="n">ASCENDC_TPL_8_BW</span><span class="p">,</span> <span class="n">ASCENDC_TPL_UI_MIX</span><span class="p">,</span> 
                          <span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">13</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">8</span><span class="p">),</span>
  
    <span class="c1">// 自定义BOOL类型模板参数</span>
    <span class="n">ASCENDC_TPL_BOOL_DECL</span><span class="p">(</span><span class="n">IS_SPLIT</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>

<p><strong>模板参数类型说明：</strong></p>

<table>
  <thead>
    <tr>
      <th>宏</th>
      <th>作用</th>
      <th>示例</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ASCENDC_TPL_DATATYPE_DECL</code></td>
      <td>定义数据类型模板参数</td>
      <td>支持float/float16/int32等</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ASCENDC_TPL_UINT_DECL</code></td>
      <td>定义无符号整数模板参数</td>
      <td>支持范围或枚举值</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ASCENDC_TPL_BOOL_DECL</code></td>
      <td>定义布尔类型模板参数</td>
      <td>支持true/false</td>
    </tr>
  </tbody>
</table>

<h4 id="722-host侧模板参数设置">7.2.2 Host侧模板参数设置</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"../op_kernel/tiling_key_clamp.h"</span><span class="cp">
</span>
<span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="nf">TilingFunc</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">TilingContext</span><span class="o">*</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 获取数据类型</span>
    <span class="n">ge</span><span class="o">::</span><span class="n">DataType</span> <span class="n">dtype_x</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputDesc</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">GetDataType</span><span class="p">();</span>
    <span class="n">ge</span><span class="o">::</span><span class="n">DataType</span> <span class="n">dtype_y</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetOutputDesc</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">GetDataType</span><span class="p">();</span>
  
    <span class="c1">// 转换为模板参数</span>
    <span class="kt">uint32_t</span> <span class="n">D_T_X</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">dtype_x</span><span class="p">);</span>
    <span class="kt">uint32_t</span> <span class="n">D_T_Y</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">dtype_y</span><span class="p">);</span>
  
    <span class="c1">// 根据数据量决定是否切分</span>
    <span class="kt">uint32_t</span> <span class="n">TILE_NUM</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">IS_SPLIT</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">totalLength</span> <span class="o">&gt;=</span> <span class="n">MIN_LENGTH_FOR_SPLIT</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">IS_SPLIT</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="n">TILE_NUM</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span>
    <span class="p">}</span>
  
    <span class="c1">// 设置模板参数（自动生成TilingKey）</span>
    <span class="n">ASCENDC_TPL_SEL_PARAM</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">D_T_X</span><span class="p">,</span> <span class="n">D_T_Y</span><span class="p">,</span> <span class="n">TILE_NUM</span><span class="p">,</span> <span class="n">IS_SPLIT</span><span class="p">);</span>
  
    <span class="k">return</span> <span class="n">ge</span><span class="o">::</span><span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>关键步骤：</strong></p>

<ol>
  <li><strong>获取数据类型</strong>：从输入输出描述中获取数据类型</li>
  <li><strong>转换模板参数</strong>：将数据类型转换为模板参数</li>
  <li><strong>计算模板参数</strong>：根据实际需求计算其他模板参数</li>
  <li><strong>设置模板参数</strong>：通过 <code class="language-plaintext highlighter-rouge">ASCENDC_TPL_SEL_PARAM</code>宏设置</li>
</ol>

<h4 id="723-device侧模板参数使用">7.2.3 Device侧模板参数使用</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"tiling_key_clamp.h"</span><span class="cp">
</span>
<span class="c1">// 模板参数作为函数参数</span>
<span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">D_T_X</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">D_T_Y</span><span class="p">,</span> <span class="kt">int</span> <span class="n">TILE_NUM</span><span class="p">,</span> <span class="kt">int</span> <span class="n">IS_SPLIT</span><span class="p">&gt;</span>
<span class="n">__global__</span> <span class="n">__aicore__</span> <span class="kt">void</span> <span class="nf">clamp</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">workspace</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">tiling</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">REGISTER_TILING_DEFAULT</span><span class="p">(</span><span class="n">ClampTilingData</span><span class="p">);</span>
    <span class="n">GET_TILING_DATA_WITH_STRUCT</span><span class="p">(</span><span class="n">ClampTilingData</span><span class="p">,</span> <span class="n">tiling_data</span><span class="p">,</span> <span class="n">tiling</span><span class="p">);</span>
  
    <span class="c1">// 根据模板参数创建算子对象</span>
    <span class="n">KernelClamp</span><span class="o">&lt;</span><span class="n">D_T_X</span><span class="p">,</span> <span class="n">D_T_Y</span><span class="o">&gt;</span> <span class="n">op</span><span class="p">;</span>
    <span class="n">op</span><span class="p">.</span><span class="n">Init</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">workspace</span><span class="p">,</span> <span class="n">tiling_data</span><span class="p">.</span><span class="n">totalLength</span><span class="p">,</span> <span class="n">TILE_NUM</span><span class="p">,</span> <span class="n">tiling_data</span><span class="p">.</span><span class="n">min</span><span class="p">);</span>
  
    <span class="c1">// 根据模板参数选择处理逻辑</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">IS_SPLIT</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">op</span><span class="p">.</span><span class="n">Process1</span><span class="p">();</span>  <span class="c1">// 不切分处理</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">IS_SPLIT</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">op</span><span class="p">.</span><span class="n">Process2</span><span class="p">();</span>  <span class="c1">// 切分处理</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>模板参数使用要点：</strong></p>

<ol>
  <li><strong>模板参数作为函数参数</strong>：在核函数签名中声明模板参数</li>
  <li><strong>去掉extern “C”</strong>：模板函数不需要C链接方式</li>
  <li><strong>根据模板参数选择逻辑</strong>：使用if语句根据模板参数选择不同的处理分支</li>
</ol>

<h3 id="73-tiling模板编程的优势">7.3 Tiling模板编程的优势</h3>

<table>
  <thead>
    <tr>
      <th>特性</th>
      <th>传统TilingKey</th>
      <th>Tiling模板编程</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>可读性</strong></td>
      <td>数字标识，难以理解</td>
      <td>有意义的名称，易于理解</td>
    </tr>
    <tr>
      <td><strong>维护性</strong></td>
      <td>手动管理，容易出错</td>
      <td>自动生成，减少错误</td>
    </tr>
    <tr>
      <td><strong>扩展性</strong></td>
      <td>添加新分支需要修改多处</td>
      <td>只需添加模板参数</td>
    </tr>
    <tr>
      <td><strong>类型安全</strong></td>
      <td>运行时检查</td>
      <td>编译时检查</td>
    </tr>
  </tbody>
</table>

<h2 id="8-完整示例clamp算子实现">8. 完整示例：Clamp算子实现</h2>

<h3 id="81-算子原型">8.1 算子原型</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>算子类型: Clamp
输入: x (Shape: [8, 2048], Type: int32/float, Format: ND)
输出: y (Shape: [8, 2048], Type: int32/float, Format: ND)
属性: min (Type: float, Default: 0)
</code></pre></div></div>

<h3 id="82-tiling数据结构定义">8.2 Tiling数据结构定义</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// clamp_tiling.h</span>
<span class="cp">#ifndef CLAMP_TILING_H
#define CLAMP_TILING_H
</span>
<span class="cp">#include</span> <span class="cpf">&lt;cstdint&gt;</span><span class="cp">
</span>
<span class="k">struct</span> <span class="nc">ClampTilingData</span> <span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">totalLength</span><span class="p">;</span>  <span class="c1">// 总数据长度</span>
    <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">;</span>      <span class="c1">// 分块数量</span>
    <span class="kt">float</span> <span class="n">min</span><span class="p">;</span>             <span class="c1">// 最小值属性</span>
<span class="p">};</span>

<span class="cp">#endif
</span></code></pre></div></div>

<h3 id="83-模板参数定义">8.3 模板参数定义</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tiling_key_clamp.h</span>
<span class="cp">#include</span> <span class="cpf">"ascendc/host_api/tiling/template_argument.h"</span><span class="cp">
</span>
<span class="n">ASCENDC_TPL_ARGS_DECL</span><span class="p">(</span><span class="n">Clamp</span><span class="p">,</span>
    <span class="n">ASCENDC_TPL_DATATYPE_DECL</span><span class="p">(</span><span class="n">D_T_X</span><span class="p">,</span> <span class="n">C_DT_FLOAT</span><span class="p">,</span> <span class="n">C_DT_FLOAT16</span><span class="p">,</span> <span class="n">ASCENDC_TPL_INPUT</span><span class="p">(</span><span class="mi">0</span><span class="p">)),</span>
    <span class="n">ASCENDC_TPL_DATATYPE_DECL</span><span class="p">(</span><span class="n">D_T_Y</span><span class="p">,</span> <span class="n">C_DT_FLOAT</span><span class="p">,</span> <span class="n">C_DT_FLOAT16</span><span class="p">,</span> <span class="n">ASCENDC_TPL_OUTPUT</span><span class="p">(</span><span class="mi">0</span><span class="p">))</span>
<span class="p">);</span>
</code></pre></div></div>

<h3 id="84-host侧实现">8.4 Host侧实现</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// clamp.cpp</span>
<span class="cp">#include</span> <span class="cpf">"../op_kernel/clamp_tiling.h"</span><span class="cp">
#include</span> <span class="cpf">"register/op_def_registry.h"</span><span class="cp">
#include</span> <span class="cpf">"../op_kernel/tiling_key_clamp.h"</span><span class="cp">
#include</span> <span class="cpf">"tiling/platform/platform_ascendc.h"</span><span class="cp">
</span>
<span class="k">namespace</span> <span class="n">optiling</span> <span class="p">{</span>
<span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">TilingFunc</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">TilingContext</span><span class="o">*</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">auto</span> <span class="n">ascendcPlatform</span> <span class="o">=</span> <span class="n">platform_ascendc</span><span class="o">::</span><span class="n">PlatformAscendC</span><span class="p">(</span><span class="n">context</span><span class="o">-&gt;</span><span class="n">GetPlatformInfo</span><span class="p">());</span>
    <span class="n">ClampTilingData</span> <span class="o">*</span><span class="n">tiling</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetTilingData</span><span class="o">&lt;</span><span class="n">ClampTilingData</span><span class="o">&gt;</span><span class="p">();</span>
  
    <span class="c1">// 获取输入信息</span>
    <span class="kt">uint32_t</span> <span class="n">totalLength</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">GetOriginShape</span><span class="p">().</span><span class="n">GetShapeSize</span><span class="p">();</span>
    <span class="n">ge</span><span class="o">::</span><span class="n">DataType</span> <span class="n">dtype_x</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputDesc</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">GetDataType</span><span class="p">();</span>
    <span class="n">ge</span><span class="o">::</span><span class="n">DataType</span> <span class="n">dtype_y</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetOutputDesc</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">GetDataType</span><span class="p">();</span>
  
    <span class="c1">// 转换为模板参数</span>
    <span class="kt">uint32_t</span> <span class="n">D_T_X</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">dtype_x</span><span class="p">);</span>
    <span class="kt">uint32_t</span> <span class="n">D_T_Y</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">dtype_y</span><span class="p">);</span>
  
    <span class="c1">// 获取属性值</span>
    <span class="kt">float</span> <span class="n">min_value</span> <span class="o">=</span> <span class="o">*</span><span class="n">context</span><span class="o">-&gt;</span><span class="n">GetAttrs</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetFloat</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
  
    <span class="c1">// 设置Tiling数据</span>
    <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">totalLength</span> <span class="o">=</span> <span class="n">totalLength</span><span class="p">;</span>
    <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span>
    <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">min</span> <span class="o">=</span> <span class="n">min_value</span><span class="p">;</span>
    <span class="n">context</span><span class="o">-&gt;</span><span class="n">SetBlockDim</span><span class="p">(</span><span class="mi">8</span><span class="p">);</span>
  
    <span class="c1">// 配置模板参数</span>
    <span class="n">ASCENDC_TPL_SEL_PARAM</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">D_T_X</span><span class="p">,</span> <span class="n">D_T_Y</span><span class="p">);</span>
  
    <span class="c1">// 配置Workspace大小</span>
    <span class="kt">size_t</span> <span class="n">userWorkspaceSize</span> <span class="o">=</span> <span class="mi">256</span> <span class="o">*</span> <span class="mi">4</span><span class="p">;</span>  <span class="c1">// 256个元素，每个4字节</span>
    <span class="kt">size_t</span> <span class="n">systemWorkspaceSize</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">size_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ascendcPlatform</span><span class="p">.</span><span class="n">GetLibApiWorkSpaceSize</span><span class="p">());</span>
    <span class="kt">size_t</span> <span class="o">*</span><span class="n">currentWorkspace</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetWorkspaceSizes</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
    <span class="n">currentWorkspace</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">userWorkspaceSize</span> <span class="o">+</span> <span class="n">systemWorkspaceSize</span><span class="p">;</span>
  
    <span class="k">return</span> <span class="n">ge</span><span class="o">::</span><span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>

<span class="k">namespace</span> <span class="n">ge</span> <span class="p">{</span>
<span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">InferShape</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">InferShapeContext</span><span class="o">*</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="n">gert</span><span class="o">::</span><span class="n">Shape</span><span class="o">*</span> <span class="n">x1_shape</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="n">gert</span><span class="o">::</span><span class="n">Shape</span><span class="o">*</span> <span class="n">y_shape</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetOutputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="o">*</span><span class="n">y_shape</span> <span class="o">=</span> <span class="o">*</span><span class="n">x1_shape</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">InferDataType</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">InferDataTypeContext</span> <span class="o">*</span><span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="k">auto</span> <span class="n">inputDataType</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputDataType</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="n">context</span><span class="o">-&gt;</span><span class="n">SetOutputDataType</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">inputDataType</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>

<span class="k">namespace</span> <span class="n">ops</span> <span class="p">{</span>
<span class="k">class</span> <span class="nc">Clamp</span> <span class="o">:</span> <span class="k">public</span> <span class="n">OpDef</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="k">explicit</span> <span class="n">Clamp</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">)</span> <span class="o">:</span> <span class="n">OpDef</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Input</span><span class="p">(</span><span class="s">"x"</span><span class="p">)</span>
            <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
            <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_INT32</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
            <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
            <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>
      
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Output</span><span class="p">(</span><span class="s">"y"</span><span class="p">)</span>
            <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
            <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_INT32</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
            <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
            <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>
      
        <span class="c1">// 注册属性</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Attr</span><span class="p">(</span><span class="s">"min"</span><span class="p">).</span><span class="n">Float</span><span class="p">();</span>
      
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">SetInferShape</span><span class="p">(</span><span class="n">ge</span><span class="o">::</span><span class="n">InferShape</span><span class="p">)</span>
            <span class="p">.</span><span class="n">SetInferDataType</span><span class="p">(</span><span class="n">ge</span><span class="o">::</span><span class="n">InferDataType</span><span class="p">);</span>
      
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">AICore</span><span class="p">()</span>
            <span class="p">.</span><span class="n">SetTiling</span><span class="p">(</span><span class="n">optiling</span><span class="o">::</span><span class="n">TilingFunc</span><span class="p">);</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">AICore</span><span class="p">().</span><span class="n">AddConfig</span><span class="p">(</span><span class="s">"ascend910b"</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="n">OP_ADD</span><span class="p">(</span><span class="n">Clamp</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="85-device侧实现">8.5 Device侧实现</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// clamp.cpp</span>
<span class="cp">#include</span> <span class="cpf">"kernel_operator.h"</span><span class="cp">
#include</span> <span class="cpf">"clamp_tiling.h"</span><span class="cp">
#include</span> <span class="cpf">"tiling_key_clamp.h"</span><span class="cp">
#include</span> <span class="cpf">"kernel_operator_dump_tensor_intf_impl.h"</span><span class="cp">
</span>
<span class="k">constexpr</span> <span class="kt">int32_t</span> <span class="n">BUFFER_NUM</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>

<span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">dtypeX</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">dtypeY</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">KernelClamp</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="n">KernelClamp</span><span class="p">()</span> <span class="p">{}</span>
  
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Init</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">workspace</span><span class="p">,</span> 
                                <span class="kt">uint32_t</span> <span class="n">totalLength</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">,</span> <span class="kt">float</span> <span class="n">min</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">=</span> <span class="n">totalLength</span> <span class="o">/</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockNum</span><span class="p">();</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">=</span> <span class="n">tileNum</span><span class="p">;</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">min</span> <span class="o">=</span> <span class="n">min</span><span class="p">;</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">/</span> <span class="n">tileNum</span> <span class="o">/</span> <span class="n">BUFFER_NUM</span><span class="p">;</span>
      
        <span class="c1">// 设置GlobalTensor</span>
        <span class="n">xGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">dtypeX</span> <span class="o">*</span><span class="p">)</span><span class="n">x</span> <span class="o">+</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">*</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span> 
                           <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span><span class="p">);</span>
        <span class="n">yGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">dtypeY</span> <span class="o">*</span><span class="p">)</span><span class="n">y</span> <span class="o">+</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">*</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span> 
                           <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span><span class="p">);</span>
      
        <span class="c1">// 设置Workspace</span>
        <span class="n">tmpGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="kt">float</span> <span class="o">*</span><span class="p">)</span><span class="n">workspace</span><span class="p">);</span>
      
        <span class="c1">// 初始化队列和TBuf</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">inQueueX</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dtypeX</span><span class="p">));</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">outQueueY</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dtypeY</span><span class="p">));</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">outQueueTmp</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">));</span>
    <span class="p">}</span>
  
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Process</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">int32_t</span> <span class="n">loopCount</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">*</span> <span class="n">BUFFER_NUM</span><span class="p">;</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">loopCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">CopyIn</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="n">Compute</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="n">CopyOut</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

<span class="nl">private:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">CopyIn</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">xLocal</span><span class="p">,</span> <span class="n">xGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">],</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>
        <span class="n">inQueueX</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
    <span class="p">}</span>
  
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Compute</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">outQueueY</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span><span class="p">();</span>
      
        <span class="c1">// 使用Mins进行Clamp计算</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">Mins</span><span class="p">(</span><span class="n">yLocal</span><span class="p">,</span> <span class="n">xLocal</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">min</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>
      
        <span class="n">outQueueY</span><span class="p">.</span><span class="n">EnQue</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
        <span class="n">inQueueX</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
    <span class="p">}</span>
  
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">CopyOut</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">outQueueY</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">yGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">],</span> <span class="n">yLocal</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>
        <span class="n">outQueueY</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
    <span class="p">}</span>

<span class="nl">private:</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TPipe</span> <span class="n">pipe</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECIN</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">inQueueX</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECOUT</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">outQueueY</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TBuf</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECCALC</span><span class="o">&gt;</span> <span class="n">outQueueTmp</span><span class="p">;</span>
  
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span> <span class="n">xGm</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span> <span class="n">yGm</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">tmpGm</span><span class="p">;</span>
  
    <span class="kt">uint32_t</span> <span class="n">blockLength</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileLength</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">min</span><span class="p">;</span>
<span class="p">};</span>

<span class="c1">// 模板核函数（注意：去掉extern "C"）</span>
<span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">D_T_X</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">D_T_Y</span><span class="p">&gt;</span>
<span class="n">__global__</span> <span class="n">__aicore__</span> <span class="kt">void</span> <span class="n">clamp</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">workspace</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">tiling</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">REGISTER_TILING_DEFAULT</span><span class="p">(</span><span class="n">ClampTilingData</span><span class="p">);</span>
    <span class="n">GET_TILING_DATA_WITH_STRUCT</span><span class="p">(</span><span class="n">ClampTilingData</span><span class="p">,</span> <span class="n">tiling_data</span><span class="p">,</span> <span class="n">tiling</span><span class="p">);</span>
  
    <span class="n">KernelClamp</span><span class="o">&lt;</span><span class="n">D_T_X</span><span class="p">,</span> <span class="n">D_T_Y</span><span class="o">&gt;</span> <span class="n">op</span><span class="p">;</span>
    <span class="n">op</span><span class="p">.</span><span class="n">Init</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">workspace</span><span class="p">,</span> <span class="n">tiling_data</span><span class="p">.</span><span class="n">totalLength</span><span class="p">,</span> <span class="n">tiling_data</span><span class="p">.</span><span class="n">tileNum</span><span class="p">,</span> <span class="n">tiling_data</span><span class="p">.</span><span class="n">min</span><span class="p">);</span>
    <span class="n">op</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="9-内存位置关系总结">9. 内存位置关系总结</h2>

<h3 id="91-完整的内存层次结构">9.1 完整的内存层次结构</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────────┐
│                    Host侧 (CPU)                          │
│  ┌─────────────────────────────────────────────────┐   │
│  │         Host Memory                             │   │
│  │  - 应用程序数据                                  │   │
│  │  - 模型参数                                      │   │
│  │  - 输入输出数据                                  │   │
│  └─────────────────────────────────────────────────┘   │
└────────────────────┬────────────────────────────────────┘
                     │ aclrtMemcpy
                     │ (数据搬运)
┌────────────────────▼────────────────────────────────────┐
│                  Device侧 (AI芯片)                       │
│  ┌─────────────────────────────────────────────────┐   │
│  │         Global Memory (HBM)                     │   │
│  │  ┌─────────────────────────────────────────┐   │   │
│  │  │  输入/输出张量                            │   │   │
│  │  │  - x (输入张量)                           │   │   │
│  │  │  - y (输出张量)                           │   │   │
│  │  └─────────────────────────────────────────┘   │   │
│  │  ┌─────────────────────────────────────────┐   │   │
│  │  │  Workspace (工作空间)                    │   │   │
│  │  │  - 系统Workspace (API预留)               │   │   │
│  │  │  - 用户Workspace (算子使用)               │   │   │
│  │  └─────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────┘   │
│                           ▲                              │
│                           │ DataCopy                     │
│  ┌─────────────────────────────────────────────────┐   │
│  │         LocalMemory (片上存储)                  │   │
│  │  ┌─────────────────────────────────────────┐   │   │
│  │  │  Unified Buffer (UB)                    │   │   │
│  │  │  ┌───────────────────────────────────┐  │   │   │
│  │  │  │  TQue (队列管理)                 │  │   │   │
│  │  │  │  - inQueueX (输入队列)            │  │   │   │
│  │  │  │  - outQueueY (输出队列)           │  │   │   │
│  │  │  └───────────────────────────────────┘  │   │   │
│  │  │  ┌───────────────────────────────────┐  │   │   │
│  │  │  │  TBuf (临时存储)                 │  │   │   │
│  │  │  │  - tmpBuf (计算过程临时存储)      │  │   │   │
│  │  │  └───────────────────────────────────┘  │   │   │
│  │  └─────────────────────────────────────────┘   │   │
│  │  ┌─────────────────────────────────────────┐   │   │
│  │  │  L1 Buffer / L0 Buffer                  │   │   │
│  │  └─────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘
</code></pre></div></div>

<h3 id="92-数据流动过程">9.2 数据流动过程</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Host侧准备数据
   └─&gt; Host Memory: 输入数据x

2. 数据搬运到Device侧
   └─&gt; aclrtMemcpy(Host → Device)
       └─&gt; Global Memory: x (输入张量)

3. 核函数执行
   └─&gt; CopyIn: Global Memory → LocalMemory
       └─&gt; TQue: inQueueX (输入队列)
   
   └─&gt; Compute: LocalMemory计算
       └─&gt; TBuf: tmpBuf (临时存储)
       └─&gt; TQue: outQueueY (输出队列)
   
   └─&gt; CopyOut: LocalMemory → Global Memory
       └─&gt; Global Memory: y (输出张量)
   
   └─&gt; Workspace使用 (如果需要)
       └─&gt; Global Memory: workspace (工作空间)

4. 结果回拷到Host侧
   └─&gt; aclrtMemcpy(Device → Host)
       └─&gt; Host Memory: 输出数据y
</code></pre></div></div>

<h3 id="93-内存访问速度对比">9.3 内存访问速度对比</h3>

<table>
  <thead>
    <tr>
      <th>内存类型</th>
      <th>访问速度</th>
      <th>容量</th>
      <th>用途</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Host Memory</strong></td>
      <td>慢</td>
      <td>大</td>
      <td>存储原始数据</td>
    </tr>
    <tr>
      <td><strong>Global Memory</strong></td>
      <td>中等</td>
      <td>大</td>
      <td>存储输入输出张量、Workspace</td>
    </tr>
    <tr>
      <td><strong>LocalMemory</strong></td>
      <td>快</td>
      <td>小</td>
      <td>计算过程中的临时数据</td>
    </tr>
    <tr>
      <td><strong>TBuf</strong></td>
      <td>最快</td>
      <td>最小</td>
      <td>计算过程中的中间结果</td>
    </tr>
  </tbody>
</table>

<h2 id="10-关键知识点总结">10. 关键知识点总结</h2>

<h3 id="101-c模板编程">10.1 C++模板编程</h3>

<table>
  <thead>
    <tr>
      <th>概念</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>函数模板</strong></td>
      <td>支持多种数据类型的函数</td>
    </tr>
    <tr>
      <td><strong>类模板</strong></td>
      <td>支持多种数据类型的类</td>
    </tr>
    <tr>
      <td><strong>模板特化</strong></td>
      <td>针对特定类型的特殊实现</td>
    </tr>
    <tr>
      <td><strong>类型推导</strong></td>
      <td>编译器自动推导模板参数类型</td>
    </tr>
  </tbody>
</table>

<h3 id="102-workspace">10.2 Workspace</h3>

<table>
  <thead>
    <tr>
      <th>概念</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>系统Workspace</strong></td>
      <td>Ascend C API需要的预留空间</td>
    </tr>
    <tr>
      <td><strong>用户Workspace</strong></td>
      <td>算子实现使用的临时空间</td>
    </tr>
    <tr>
      <td><strong>内存位置</strong></td>
      <td>Global Memory (HBM)</td>
    </tr>
    <tr>
      <td><strong>配置方式</strong></td>
      <td>Host侧Tiling函数中设置大小</td>
    </tr>
  </tbody>
</table>

<h3 id="103-tbuf">10.3 TBuf</h3>

<table>
  <thead>
    <tr>
      <th>概念</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>作用</strong></td>
      <td>LocalMemory上的临时存储</td>
    </tr>
    <tr>
      <td><strong>内存位置</strong></td>
      <td>LocalMemory (片上)</td>
    </tr>
    <tr>
      <td><strong>初始化</strong></td>
      <td>pipe.InitBuffer()</td>
    </tr>
    <tr>
      <td><strong>获取方式</strong></td>
      <td>Get<code class="language-plaintext highlighter-rouge">&lt;T&gt;</code>()</td>
    </tr>
    <tr>
      <td><strong>释放方式</strong></td>
      <td>无需手动释放</td>
    </tr>
  </tbody>
</table>

<h3 id="104-算子属性">10.4 算子属性</h3>

<table>
  <thead>
    <tr>
      <th>概念</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>必选属性</strong></td>
      <td>算子运行的核心前提</td>
    </tr>
    <tr>
      <td><strong>可选属性</strong></td>
      <td>算子的辅助配置</td>
    </tr>
    <tr>
      <td><strong>静态性</strong></td>
      <td>运行过程中不可修改</td>
    </tr>
    <tr>
      <td><strong>解耦性</strong></td>
      <td>与输入张量相互独立</td>
    </tr>
  </tbody>
</table>

<h3 id="105-tiling模板编程">10.5 Tiling模板编程</h3>

<table>
  <thead>
    <tr>
      <th>概念</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>TilingKey</strong></td>
      <td>区分不同实现的标识</td>
    </tr>
    <tr>
      <td><strong>模板参数</strong></td>
      <td>替代数字TilingKey的有意义名称</td>
    </tr>
    <tr>
      <td><strong>数据类型参数</strong></td>
      <td>ASCENDC_TPL_DATATYPE_DECL</td>
    </tr>
    <tr>
      <td><strong>整数参数</strong></td>
      <td>ASCENDC_TPL_UINT_DECL</td>
    </tr>
    <tr>
      <td><strong>布尔参数</strong></td>
      <td>ASCENDC_TPL_BOOL_DECL</td>
    </tr>
    <tr>
      <td><strong>Host侧设置</strong></td>
      <td>ASCENDC_TPL_SEL_PARAM</td>
    </tr>
    <tr>
      <td><strong>Device侧使用</strong></td>
      <td>模板参数作为函数参数</td>
    </tr>
  </tbody>
</table>

<p><strong>参考文献：</strong></p>

<ul>
  <li>华为昇腾 CANN 官方文档 (v6.3.0+)</li>
  <li>《Ascend C 编程指南》</li>
  <li>《AscendCL 接口参考》</li>
  <li>《C++ Templates: The Complete Guide》</li>
  <li>PyTorch官方文档</li>
</ul>]]></content><author><name>溄</name><email>hongqiang.yan@qq.com</email></author><category term="技术博客" /><category term="CANN" /><category term="华为昇腾" /><category term="算子开发" /><category term="Tiling模板" /><category term="AscendC" /><category term="C++" /><category term="Workspace" /><category term="TBuf" /><summary type="html"><![CDATA[Tiling 模板、属性、TBuf 与 Workspace 的进阶开发全解析。]]></summary></entry><entry><title type="html">华为昇腾CANN算子Tiling策略实战：大小核动态负载均衡的减法算子实现</title><link href="https://yhongq.github.io/posts/cann-tiling-strategy/" rel="alternate" type="text/html" title="华为昇腾CANN算子Tiling策略实战：大小核动态负载均衡的减法算子实现" /><published>2026-05-22T00:00:00+08:00</published><updated>2026-05-22T00:00:00+08:00</updated><id>https://yhongq.github.io/posts/cann-tiling-strategy</id><content type="html" xml:base="https://yhongq.github.io/posts/cann-tiling-strategy/"><![CDATA[<h1 id="华为昇腾cann算子tiling策略实战大小核动态负载均衡的减法算子实现">华为昇腾CANN算子Tiling策略实战：大小核动态负载均衡的减法算子实现</h1>

<blockquote>
  <p>本文通过一个完整的减法算子（<code class="language-plaintext highlighter-rouge">z = x - y</code>）代码，深入讲解基于华为昇腾CANN的Tiling策略设计。从Host侧的动态负载均衡算法，到Device侧的多核并行处理，带你一步步理解昇腾AI芯片的Tiling策略和内存管理。</p>
</blockquote>

<h2 id="1-背景与目标">1. 背景与目标</h2>

<p>在昇腾AI处理器的开发中，<strong>Tiling策略</strong>是算子性能优化的关键。当输入数据量不能被核心数整除时，如何合理分配数据给各个核心，避免核心空闲，提升整体计算效率，是Tiling策略要解决的核心问题。本文的目标是：</p>

<ul>
  <li>实现一个减法算子 <code class="language-plaintext highlighter-rouge">SubCustomTemplate</code>，支持 <code class="language-plaintext highlighter-rouge">z[i] = x[i] - y[i]</code>。</li>
  <li>采用<strong>大小核动态负载均衡策略</strong>，解决数据量不能被核心数整除的问题。</li>
  <li>深入理解Unified Buffer、Tile、Block等昇腾AI芯片的核心概念。</li>
</ul>

<h2 id="2-tiling策略核心思想">2. Tiling策略核心思想</h2>

<h3 id="21-大小核概念">2.1 大小核概念</h3>

<p>在昇腾AI处理器中，”大核”和”小核”并非指核心能力差异，而是指<strong>处理数据量的多少</strong>：</p>

<ul>
  <li><strong>大核</strong>：处理较多的数据，用于处理尾数据（数据量不能被核心数整除时的剩余部分）</li>
  <li><strong>小核</strong>：处理较少的数据，用于处理主体数据</li>
</ul>

<p>这种设计确保了所有核心都能充分利用，避免部分核心空闲。</p>

<h3 id="22-核心概念">2.2 核心概念</h3>

<table>
  <thead>
    <tr>
      <th>概念</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>BLOCK_SIZE</strong></td>
      <td>块大小，每次搬运/计算的最小数据量，通常为32字节对齐</td>
    </tr>
    <tr>
      <td><strong>Tile</strong></td>
      <td>每次搬运的数据量，由Unified Buffer大小决定</td>
    </tr>
    <tr>
      <td><strong>Unified Buffer</strong></td>
      <td>核内快速缓存，计算时能使用的空间大小</td>
    </tr>
    <tr>
      <td><strong>尾数据</strong></td>
      <td>数据量不能被Tile整除时的剩余数据</td>
    </tr>
  </tbody>
</table>

<h2 id="3-host侧tiling策略实现">3. Host侧Tiling策略实现</h2>

<h3 id="31-数据对齐与核心数确定">3.1 数据对齐与核心数确定</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint32_t</span> <span class="n">inputNum</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">GetStorageShape</span><span class="p">().</span><span class="n">GetShapeSize</span><span class="p">();</span>
<span class="kt">uint32_t</span> <span class="n">typeLength</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">ge</span><span class="o">::</span><span class="n">TypeUtils</span><span class="o">::</span><span class="n">GetDataTypeLength</span><span class="p">(</span><span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputDesc</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">GetDataType</span><span class="p">(),</span> <span class="n">typeLength</span><span class="p">);</span>
<span class="kt">uint32_t</span> <span class="n">inputLength</span> <span class="o">=</span> <span class="n">inputNum</span> <span class="o">*</span> <span class="n">typeLength</span><span class="p">;</span>

<span class="k">const</span> <span class="kt">uint32_t</span> <span class="n">BLOCK_SIZE</span> <span class="o">=</span> <span class="mi">32</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">inputLengthAlgin32</span> <span class="o">=</span> <span class="p">(((</span><span class="n">inputLength</span> <span class="o">+</span> <span class="n">BLOCK_SIZE</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">/</span> <span class="n">BLOCK_SIZE</span><span class="p">)</span> <span class="o">*</span> <span class="n">BLOCK_SIZE</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>关键点</strong>：</p>
<ul>
  <li>获取输入数据的元素数量和数据类型长度</li>
  <li>计算输入数据的字节长度</li>
  <li>按照32字节对齐，保证每个核心至少处理一个块的数据</li>
</ul>

<h3 id="32-核心数动态分配">3.2 核心数动态分配</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">auto</span> <span class="n">min_Use_CoreNum</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">min</span><span class="p">(</span><span class="n">coreNum</span><span class="p">,</span> <span class="n">inputLengthAlgin32</span> <span class="o">/</span> <span class="n">BLOCK_SIZE</span><span class="p">);</span>
<span class="n">coreNum</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">max</span><span class="p">(</span><span class="n">min_Use_CoreNum</span><span class="p">,</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">uint32_t</span><span class="o">&gt;</span><span class="p">(</span><span class="mi">1</span><span class="p">));</span>
<span class="n">context</span><span class="o">-&gt;</span><span class="n">SetBlockDim</span><span class="p">(</span><span class="n">coreNum</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>核心逻辑</strong>：</p>
<ul>
  <li>每个核至少处理一个块的数据量</li>
  <li>如果对齐后的输入数据块数小于核心数，只使用block数的核心数</li>
  <li>至少使用一个核心，避免0个核心处理数据异常</li>
</ul>

<h3 id="33-数据分配策略">3.3 数据分配策略</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint32_t</span> <span class="n">everyCoreInputBlockNum</span> <span class="o">=</span> <span class="n">inputLengthAlgin32</span> <span class="o">/</span> <span class="n">BLOCK_SIZE</span> <span class="o">/</span> <span class="n">coreNum</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">tailBlockNum</span> <span class="o">=</span> <span class="p">(</span><span class="n">inputLengthAlgin32</span> <span class="o">/</span> <span class="n">BLOCK_SIZE</span><span class="p">)</span> <span class="o">%</span> <span class="n">coreNum</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>分配策略</strong>：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">everyCoreInputBlockNum</code>：每个核心处理的块数量（平均分配部分）</li>
  <li><code class="language-plaintext highlighter-rouge">tailBlockNum</code>：尾块数量，如果输入数据块数不能被核心数整除，前<code class="language-plaintext highlighter-rouge">tailBlockNum</code>个核心需要多处理一个块</li>
</ul>

<h3 id="34-tile计算">3.4 Tile计算</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint64_t</span> <span class="n">ubSize</span><span class="p">;</span>
<span class="n">ascendcPlatform</span><span class="p">.</span><span class="n">GetCoreMemSize</span><span class="p">(</span><span class="n">platform_ascendc</span><span class="o">::</span><span class="n">CoreMemType</span><span class="o">::</span><span class="n">UB</span><span class="p">,</span> <span class="n">ubSize</span><span class="p">);</span>

<span class="kt">uint32_t</span> <span class="n">ubDataNumber</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">tileBlockNum</span> <span class="o">=</span> <span class="p">(</span><span class="n">ubSize</span> <span class="o">/</span> <span class="n">BLOCK_SIZE</span><span class="p">)</span> <span class="o">/</span> <span class="n">ubDataNumber</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">tileDataNum</span> <span class="o">=</span> <span class="p">(</span><span class="n">tileBlockNum</span> <span class="o">*</span> <span class="n">BLOCK_SIZE</span><span class="p">)</span> <span class="o">/</span> <span class="n">typeLength</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>Tile计算逻辑</strong>：</p>
<ul>
  <li>获取Unified Buffer的大小</li>
  <li><code class="language-plaintext highlighter-rouge">ubDataNumber = 3</code>：减法涉及两个输入一个输出，需要同时在UB中存放</li>
  <li><code class="language-plaintext highlighter-rouge">tileBlockNum</code>：每次搬运的Block数量（核每次最大的计算量）</li>
  <li><code class="language-plaintext highlighter-rouge">tileDataNum</code>：每次搬运的元素数量</li>
</ul>

<h3 id="35-大小核数据分配">3.5 大小核数据分配</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint32_t</span> <span class="n">smallCoreDataNum</span> <span class="o">=</span> <span class="n">everyCoreInputBlockNum</span> <span class="o">*</span> <span class="n">BLOCK_SIZE</span> <span class="o">/</span> <span class="n">typeLength</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">smallTileNum</span> <span class="o">=</span> <span class="n">everyCoreInputBlockNum</span> <span class="o">/</span> <span class="n">tileBlockNum</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">finalSmallTileNum</span> <span class="o">=</span> <span class="p">(</span><span class="n">everyCoreInputBlockNum</span> <span class="o">%</span> <span class="n">tileBlockNum</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">smallTileNum</span> <span class="o">:</span> <span class="n">smallTileNum</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">smallTailDataNum</span> <span class="o">=</span> <span class="n">smallCoreDataNum</span> <span class="o">-</span> <span class="p">(</span><span class="n">tileDataNum</span> <span class="o">*</span> <span class="n">smallTileNum</span><span class="p">);</span>
<span class="n">smallTailDataNum</span> <span class="o">=</span> <span class="n">smallTailDataNum</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">tileDataNum</span> <span class="o">:</span> <span class="n">smallTailDataNum</span><span class="p">;</span>

<span class="n">everyCoreInputBlockNum</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">bigCoreDataNum</span> <span class="o">=</span> <span class="n">everyCoreInputBlockNum</span> <span class="o">*</span> <span class="n">BLOCK_SIZE</span> <span class="o">/</span> <span class="n">typeLength</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">bigTileNum</span> <span class="o">=</span> <span class="n">everyCoreInputBlockNum</span> <span class="o">/</span> <span class="n">tileBlockNum</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">finalBigTileNum</span> <span class="o">=</span> <span class="p">(</span><span class="n">everyCoreInputBlockNum</span> <span class="o">%</span> <span class="n">tileBlockNum</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">bigTileNum</span> <span class="o">:</span> <span class="n">bigTileNum</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">bigTailDataNum</span> <span class="o">=</span> <span class="n">bigCoreDataNum</span> <span class="o">-</span> <span class="p">(</span><span class="n">tileDataNum</span> <span class="o">*</span> <span class="n">bigTileNum</span><span class="p">);</span>
<span class="n">bigTailDataNum</span> <span class="o">=</span> <span class="n">bigTailDataNum</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">tileDataNum</span> <span class="o">:</span> <span class="n">bigTailDataNum</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>大小核分配策略</strong>：</p>
<ul>
  <li><strong>小核</strong>：处理<code class="language-plaintext highlighter-rouge">everyCoreInputBlockNum</code>个Block的数据</li>
  <li><strong>大核</strong>：处理<code class="language-plaintext highlighter-rouge">everyCoreInputBlockNum + 1</code>个Block的数据（多处理一个Block）</li>
  <li><strong>尾数据处理</strong>：如果数据量不能被Tile整除，最后一次搬运处理剩余数据</li>
</ul>

<h2 id="4-device侧多核并行实现">4. Device侧多核并行实现</h2>

<h3 id="41-核函数初始化">4.1 核函数初始化</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint32_t</span> <span class="n">coreNum</span> <span class="o">=</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">();</span>
<span class="kt">uint32_t</span> <span class="n">globalBufferIndex</span> <span class="o">=</span> <span class="n">bigCoreDataNum</span> <span class="o">*</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">();</span>

<span class="k">if</span> <span class="p">(</span><span class="n">coreNum</span> <span class="o">&lt;</span> <span class="n">tailBlockNum</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="o">-&gt;</span><span class="n">coreDataNum</span> <span class="o">=</span> <span class="n">bigCoreDataNum</span><span class="p">;</span>
    <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">=</span> <span class="n">finalBigTileNum</span><span class="p">;</span>
    <span class="k">this</span><span class="o">-&gt;</span><span class="n">tailDataNum</span> <span class="o">=</span> <span class="n">bigTailDataNum</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">this</span><span class="o">-&gt;</span><span class="n">coreDataNum</span> <span class="o">=</span> <span class="n">smallCoreDataNum</span><span class="p">;</span>
    <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">=</span> <span class="n">finalSmallTileNum</span><span class="p">;</span>
    <span class="k">this</span><span class="o">-&gt;</span><span class="n">tailDataNum</span> <span class="o">=</span> <span class="n">smallTailDataNum</span><span class="p">;</span>
    <span class="n">globalBufferIndex</span> <span class="o">-=</span> <span class="p">(</span><span class="n">bigCoreDataNum</span> <span class="o">-</span> <span class="n">smallCoreDataNum</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">()</span> <span class="o">-</span> <span class="n">tailBlockNum</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>核心识别与数据分配</strong>：</p>
<ul>
  <li>通过<code class="language-plaintext highlighter-rouge">GetBlockIdx()</code>获取当前核的编号</li>
  <li>前<code class="language-plaintext highlighter-rouge">tailBlockNum</code>个核作为大核，处理更多数据</li>
  <li>后续核作为小核，处理较少数据</li>
  <li>小核需要调整偏移量，确保从正确的位置开始处理数据</li>
</ul>

<h3 id="42-流水线并行处理">4.2 流水线并行处理</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">Process</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">int32_t</span> <span class="n">loopCount</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span><span class="p">;</span>
    <span class="k">this</span><span class="o">-&gt;</span><span class="n">processDataNum</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileDataNum</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">loopCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="o">==</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">processDataNum</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tailDataNum</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">CopyIn</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
        <span class="n">Compute</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
        <span class="n">CopyOut</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>处理流程</strong>：</p>
<ul>
  <li>循环处理每个Tile</li>
  <li>最后一个Tile处理尾数据</li>
  <li>采用CopyIn → Compute → CopyOut的流水线模式</li>
</ul>

<h3 id="43-数据搬运与计算">4.3 数据搬运与计算</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">CopyIn</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">TYPE_X</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">TYPE_X</span><span class="o">&gt;</span><span class="p">();</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">TYPE_Y</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">inQueueY</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">TYPE_Y</span><span class="o">&gt;</span><span class="p">();</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">xLocal</span><span class="p">,</span> <span class="n">xGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileDataNum</span><span class="p">],</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">processDataNum</span><span class="p">);</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">yLocal</span><span class="p">,</span> <span class="n">yGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileDataNum</span><span class="p">],</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">processDataNum</span><span class="p">);</span>
    <span class="n">inQueueX</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
    <span class="n">inQueueY</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Compute</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">TYPE_X</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">TYPE_X</span><span class="o">&gt;</span><span class="p">();</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">TYPE_Y</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">inQueueY</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">TYPE_Y</span><span class="o">&gt;</span><span class="p">();</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">TYPE_Z</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="n">outQueueZ</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">TYPE_Z</span><span class="o">&gt;</span><span class="p">();</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">Sub</span><span class="p">(</span><span class="n">zLocal</span><span class="p">,</span> <span class="n">xLocal</span><span class="p">,</span> <span class="n">yLocal</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">processDataNum</span><span class="p">);</span>
    <span class="n">outQueueZ</span><span class="p">.</span><span class="n">EnQue</span><span class="o">&lt;</span><span class="n">TYPE_Z</span><span class="o">&gt;</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>
    <span class="n">inQueueX</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
    <span class="n">inQueueY</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">CopyOut</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">TYPE_Z</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="n">outQueueZ</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">TYPE_Z</span><span class="o">&gt;</span><span class="p">();</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">zGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileDataNum</span><span class="p">],</span> <span class="n">zLocal</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">processDataNum</span><span class="p">);</span>
    <span class="n">outQueueZ</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>流水线设计</strong>：</p>
<ul>
  <li><strong>CopyIn</strong>：从GlobalMemory搬运数据到LocalTensor</li>
  <li><strong>Compute</strong>：执行减法计算，结果放入输出队列</li>
  <li><strong>CopyOut</strong>：将结果写回GlobalMemory</li>
</ul>

<h2 id="5-tiling数据结构">5. Tiling数据结构</h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">SubCustomTemplateTilingData</span> <span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">smallCoreDataNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">bigCoreDataNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">finalBigTileNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">finalSmallTileNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileDataNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">smallTailDataNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">bigTailDataNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tailBlockNum</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p><strong>结构体字段说明</strong>：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">smallCoreDataNum</code>：每个小核处理的数据个数</li>
  <li><code class="language-plaintext highlighter-rouge">bigCoreDataNum</code>：每个大核处理的数据个数</li>
  <li><code class="language-plaintext highlighter-rouge">finalBigTileNum</code>：大核搬运Tile的次数</li>
  <li><code class="language-plaintext highlighter-rouge">finalSmallTileNum</code>：小核搬运Tile的次数</li>
  <li><code class="language-plaintext highlighter-rouge">tileDataNum</code>：每个Tile处理的数据个数</li>
  <li><code class="language-plaintext highlighter-rouge">smallTailDataNum</code>：小核最后一轮搬运Tile的数据个数</li>
  <li><code class="language-plaintext highlighter-rouge">bigTailDataNum</code>：大核最后一轮搬运Tile的数据个数</li>
  <li><code class="language-plaintext highlighter-rouge">tailBlockNum</code>：处理尾数据的大核数量</li>
</ul>

<h2 id="6-关键知识点总结">6. 关键知识点总结</h2>

<table>
  <thead>
    <tr>
      <th>概念</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>大小核策略</strong></td>
      <td>通过让部分核心处理更多数据，解决数据量不能被核心数整除的问题</td>
    </tr>
    <tr>
      <td><strong>BLOCK_SIZE</strong></td>
      <td>32字节对齐的块大小，昇腾AI处理器的最小数据搬运单位</td>
    </tr>
    <tr>
      <td><strong>Unified Buffer</strong></td>
      <td>核内快速缓存，决定了一次最多能计算的数据量</td>
    </tr>
    <tr>
      <td><strong>Tile</strong></td>
      <td>每次搬运的数据量，由Unified Buffer大小和算法需求决定</td>
    </tr>
    <tr>
      <td><strong>尾数据处理</strong></td>
      <td>当数据量不能被Tile整除时，最后一次搬运处理剩余数据</td>
    </tr>
    <tr>
      <td><strong>流水线并行</strong></td>
      <td>CopyIn、Compute、CopyOut重叠进行，提升硬件利用率</td>
    </tr>
  </tbody>
</table>

<h2 id="7-算子注册">7. 算子注册</h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">SubCustomTemplate</span> <span class="o">:</span> <span class="k">public</span> <span class="n">OpDef</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="k">explicit</span> <span class="n">SubCustomTemplate</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">)</span> <span class="o">:</span> <span class="n">OpDef</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Input</span><span class="p">(</span><span class="s">"x"</span><span class="p">)</span>
            <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
            <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT16</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
            <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
            <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Input</span><span class="p">(</span><span class="s">"y"</span><span class="p">)</span>
            <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
            <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT16</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
            <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
            <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Output</span><span class="p">(</span><span class="s">"z"</span><span class="p">)</span>
            <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
            <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT16</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
            <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
            <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>

        <span class="k">this</span><span class="o">-&gt;</span><span class="n">SetInferShape</span><span class="p">(</span><span class="n">ge</span><span class="o">::</span><span class="n">InferShape</span><span class="p">).</span><span class="n">SetInferDataType</span><span class="p">(</span><span class="n">ge</span><span class="o">::</span><span class="n">InferDataType</span><span class="p">);</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">AICore</span><span class="p">().</span><span class="n">SetTiling</span><span class="p">(</span><span class="n">optiling</span><span class="o">::</span><span class="n">TilingFunc</span><span class="p">);</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">AICore</span><span class="p">().</span><span class="n">AddConfig</span><span class="p">(</span><span class="s">"ascend910b"</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>

<p><strong>算子注册要点</strong>：</p>
<ul>
  <li>定义输入输出张量的数据类型和格式</li>
  <li>设置形状推理和数据类型推理函数</li>
  <li>绑定Tiling函数</li>
  <li>指定支持的硬件平台（ascend910b）</li>
</ul>

<h2 id="8-性能优化建议">8. 性能优化建议</h2>

<ol>
  <li><strong>合理设置Tile大小</strong>：根据Unified Buffer大小和算法需求，平衡数据搬运和计算效率</li>
  <li><strong>避免核心空闲</strong>：通过大小核策略，确保所有核心都有数据可处理</li>
  <li><strong>内存对齐</strong>：按照32字节对齐，提升数据搬运效率</li>
  <li><strong>流水线并行</strong>：充分利用CopyIn、Compute、CopyOut的重叠，提升吞吐量</li>
</ol>

<hr />

<p><strong>参考文献</strong>：</p>
<ul>
  <li>华为昇腾 CANN 官方文档 (v6.3.0+)</li>
  <li>《Ascend C 编程指南》</li>
  <li>《AscendCL 接口参考》</li>
</ul>

<h2 id="完整源码">完整源码</h2>

<h3 id="sub_template_tilingh">Sub_template_tiling.h</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifndef SUB_CUSTOM_TEMPLATE_TILING_H
#define SUB_CUSTOM_TEMPLATE_TILING_H
#include</span> <span class="cpf">&lt;cstdint&gt;</span><span class="cp">
</span>
<span class="k">struct</span> <span class="nc">SubCustomTemplateTilingData</span> <span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">smallCoreDataNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">bigCoreDataNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">finalBigTileNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">finalSmallTileNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileDataNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">smallTailDataNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">bigTailDataNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tailBlockNum</span><span class="p">;</span>
<span class="p">};</span>

<span class="cp">#endif
</span></code></pre></div></div>

<h3 id="sub_template_hostcpp">Sub_template_host.cpp</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"register/op_def_registry.h"</span><span class="cp">
#include</span> <span class="cpf">"../op_kernel/sub_custom_template_tiling.h"</span><span class="cp">
#include</span> <span class="cpf">"graph/utils/type_utils.h"</span><span class="cp">
#include</span> <span class="cpf">"tiling/platform/platform_ascendc.h"</span><span class="cp">
</span>
<span class="k">namespace</span> <span class="n">optiling</span> <span class="p">{</span>
<span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">TilingFunc</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">TilingContext</span><span class="o">*</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
   <span class="k">auto</span> <span class="n">ascendcPlatform</span> <span class="o">=</span> <span class="n">platform_ascendc</span><span class="o">::</span><span class="n">PlatformAscendC</span><span class="p">(</span><span class="n">context</span><span class="o">-&gt;</span><span class="n">GetPlatformInfo</span><span class="p">());</span>
   <span class="k">auto</span> <span class="n">coreNum</span> <span class="o">=</span> <span class="n">ascendcPlatform</span><span class="p">.</span><span class="n">GetCoreNum</span><span class="p">();</span>

   <span class="kt">uint32_t</span> <span class="n">inputNum</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">GetStorageShape</span><span class="p">().</span><span class="n">GetShapeSize</span><span class="p">();</span>
   <span class="kt">uint32_t</span> <span class="n">typeLength</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
   <span class="n">ge</span><span class="o">::</span><span class="n">TypeUtils</span><span class="o">::</span><span class="n">GetDataTypeLength</span><span class="p">(</span><span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputDesc</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">GetDataType</span><span class="p">(),</span> <span class="n">typeLength</span><span class="p">);</span>
   <span class="kt">uint32_t</span> <span class="n">inputLength</span> <span class="o">=</span> <span class="n">inputNum</span> <span class="o">*</span> <span class="n">typeLength</span><span class="p">;</span>
   
   <span class="k">const</span> <span class="kt">uint32_t</span> <span class="n">BLOCK_SIZE</span> <span class="o">=</span> <span class="mi">32</span><span class="p">;</span>

   <span class="kt">uint32_t</span> <span class="n">inputLengthAlgin32</span> <span class="o">=</span> <span class="p">(((</span><span class="n">inputLength</span> <span class="o">+</span> <span class="n">BLOCK_SIZE</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">/</span> <span class="n">BLOCK_SIZE</span><span class="p">)</span> <span class="o">*</span> <span class="n">BLOCK_SIZE</span><span class="p">);</span>
   
   <span class="k">auto</span> <span class="n">min_Use_CoreNum</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">min</span><span class="p">(</span><span class="n">coreNum</span><span class="p">,</span> <span class="n">inputLengthAlgin32</span> <span class="o">/</span> <span class="n">BLOCK_SIZE</span><span class="p">);</span>
   <span class="n">coreNum</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">max</span><span class="p">(</span><span class="n">min_Use_CoreNum</span><span class="p">,</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">uint32_t</span><span class="o">&gt;</span><span class="p">(</span><span class="mi">1</span><span class="p">));</span>
   <span class="n">context</span><span class="o">-&gt;</span><span class="n">SetBlockDim</span><span class="p">(</span><span class="n">coreNum</span><span class="p">);</span>

   <span class="kt">uint32_t</span> <span class="n">everyCoreInputBlockNum</span> <span class="o">=</span> <span class="n">inputLengthAlgin32</span> <span class="o">/</span> <span class="n">BLOCK_SIZE</span> <span class="o">/</span> <span class="n">coreNum</span><span class="p">;</span>
   <span class="kt">uint32_t</span> <span class="n">tailBlockNum</span> <span class="o">=</span> <span class="p">(</span><span class="n">inputLengthAlgin32</span> <span class="o">/</span> <span class="n">BLOCK_SIZE</span><span class="p">)</span> <span class="o">%</span> <span class="n">coreNum</span><span class="p">;</span>

   <span class="kt">uint64_t</span> <span class="n">ubSize</span><span class="p">;</span>
   <span class="n">ascendcPlatform</span><span class="p">.</span><span class="n">GetCoreMemSize</span><span class="p">(</span><span class="n">platform_ascendc</span><span class="o">::</span><span class="n">CoreMemType</span><span class="o">::</span><span class="n">UB</span><span class="p">,</span> <span class="n">ubSize</span><span class="p">);</span>

   <span class="kt">uint32_t</span> <span class="n">ubDataNumber</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
   <span class="kt">uint32_t</span> <span class="n">tileBlockNum</span> <span class="o">=</span> <span class="p">(</span><span class="n">ubSize</span> <span class="o">/</span> <span class="n">BLOCK_SIZE</span> <span class="p">)</span> <span class="o">/</span> <span class="n">ubDataNumber</span><span class="p">;</span>
   <span class="kt">uint32_t</span> <span class="n">tileDataNum</span> <span class="o">=</span> <span class="p">(</span><span class="n">tileBlockNum</span> <span class="o">*</span> <span class="n">BLOCK_SIZE</span><span class="p">)</span> <span class="o">/</span> <span class="n">typeLength</span><span class="p">;</span>
   <span class="kt">uint32_t</span> <span class="n">smallCoreDataNum</span> <span class="o">=</span> <span class="n">everyCoreInputBlockNum</span> <span class="o">*</span> <span class="n">BLOCK_SIZE</span> <span class="o">/</span> <span class="n">typeLength</span><span class="p">;</span>
   <span class="kt">uint32_t</span> <span class="n">smallTileNum</span> <span class="o">=</span> <span class="n">everyCoreInputBlockNum</span> <span class="o">/</span> <span class="n">tileBlockNum</span><span class="p">;</span>
   <span class="kt">uint32_t</span> <span class="n">finalSmallTileNum</span> <span class="o">=</span> <span class="p">(</span><span class="n">everyCoreInputBlockNum</span> <span class="o">%</span> <span class="n">tileBlockNum</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">smallTileNum</span> <span class="o">:</span> <span class="n">smallTileNum</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
   <span class="kt">uint32_t</span> <span class="n">smallTailDataNum</span> <span class="o">=</span> <span class="n">smallCoreDataNum</span> <span class="o">-</span> <span class="p">(</span><span class="n">tileDataNum</span> <span class="o">*</span> <span class="n">smallTileNum</span><span class="p">);</span>
   <span class="n">smallTailDataNum</span> <span class="o">=</span> <span class="n">smallTailDataNum</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">tileDataNum</span> <span class="o">:</span> <span class="n">smallTailDataNum</span><span class="p">;</span>

   <span class="n">everyCoreInputBlockNum</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
   <span class="kt">uint32_t</span> <span class="n">bigCoreDataNum</span> <span class="o">=</span> <span class="n">everyCoreInputBlockNum</span> <span class="o">*</span> <span class="n">BLOCK_SIZE</span> <span class="o">/</span> <span class="n">typeLength</span><span class="p">;</span>
   <span class="kt">uint32_t</span> <span class="n">bigTileNum</span> <span class="o">=</span> <span class="n">everyCoreInputBlockNum</span> <span class="o">/</span> <span class="n">tileBlockNum</span><span class="p">;</span>
   <span class="kt">uint32_t</span> <span class="n">finalBigTileNum</span> <span class="o">=</span> <span class="p">(</span><span class="n">everyCoreInputBlockNum</span> <span class="o">%</span> <span class="n">tileBlockNum</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">bigTileNum</span> <span class="o">:</span> <span class="n">bigTileNum</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
   <span class="kt">uint32_t</span> <span class="n">bigTailDataNum</span> <span class="o">=</span> <span class="n">bigCoreDataNum</span> <span class="o">-</span> <span class="p">(</span><span class="n">tileDataNum</span> <span class="o">*</span> <span class="n">bigTileNum</span><span class="p">);</span>
   <span class="n">bigTailDataNum</span> <span class="o">=</span> <span class="n">bigTailDataNum</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">tileDataNum</span> <span class="o">:</span> <span class="n">bigTailDataNum</span><span class="p">;</span>

   <span class="n">SubCustomTemplateTilingData</span> <span class="o">*</span><span class="n">tiling</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetTilingData</span><span class="o">&lt;</span><span class="n">SubCustomTemplateTilingData</span><span class="o">&gt;</span><span class="p">();</span>
   <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">smallCoreDataNum</span> <span class="o">=</span> <span class="n">smallCoreDataNum</span><span class="p">;</span>
   <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">bigCoreDataNum</span> <span class="o">=</span> <span class="n">bigCoreDataNum</span><span class="p">;</span>
    <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">tileDataNum</span> <span class="o">=</span> <span class="n">tileDataNum</span><span class="p">;</span>
    <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">smallTailDataNum</span> <span class="o">=</span> <span class="n">smallTailDataNum</span><span class="p">;</span>
    <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">bigTailDataNum</span> <span class="o">=</span> <span class="n">bigTailDataNum</span><span class="p">;</span>
    <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">finalSmallTileNum</span> <span class="o">=</span> <span class="n">finalSmallTileNum</span><span class="p">;</span>
    <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">finalBigTileNum</span> <span class="o">=</span> <span class="n">finalBigTileNum</span><span class="p">;</span>
    <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">tailBlockNum</span> <span class="o">=</span> <span class="n">tailBlockNum</span><span class="p">;</span>

    <span class="k">return</span> <span class="n">ge</span><span class="o">::</span><span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">namespace</span> <span class="n">ge</span> <span class="p">{</span>
<span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">InferShape</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">InferShapeContext</span><span class="o">*</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="n">gert</span><span class="o">::</span><span class="n">Shape</span><span class="o">*</span> <span class="n">x1_shape</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="n">gert</span><span class="o">::</span><span class="n">Shape</span><span class="o">*</span> <span class="n">y_shape</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetOutputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="o">*</span><span class="n">y_shape</span> <span class="o">=</span> <span class="o">*</span><span class="n">x1_shape</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">InferDataType</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">InferDataTypeContext</span> <span class="o">*</span><span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="k">auto</span> <span class="n">inputDataType</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputDataType</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="n">context</span><span class="o">-&gt;</span><span class="n">SetOutputDataType</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">inputDataType</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">ge</span><span class="o">::</span><span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>

<span class="k">namespace</span> <span class="n">ops</span> <span class="p">{</span>
<span class="k">class</span> <span class="nc">SubCustomTemplate</span> <span class="o">:</span> <span class="k">public</span> <span class="n">OpDef</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="k">explicit</span> <span class="n">SubCustomTemplate</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">)</span> <span class="o">:</span> <span class="n">OpDef</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Input</span><span class="p">(</span><span class="s">"x"</span><span class="p">)</span>
            <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
            <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT16</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
            <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
            <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Input</span><span class="p">(</span><span class="s">"y"</span><span class="p">)</span>
            <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
            <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT16</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
            <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
            <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">Output</span><span class="p">(</span><span class="s">"z"</span><span class="p">)</span>
            <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
            <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT16</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
            <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
            <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>

        <span class="k">this</span><span class="o">-&gt;</span><span class="n">SetInferShape</span><span class="p">(</span><span class="n">ge</span><span class="o">::</span><span class="n">InferShape</span><span class="p">).</span><span class="n">SetInferDataType</span><span class="p">(</span><span class="n">ge</span><span class="o">::</span><span class="n">InferDataType</span><span class="p">);</span>

        <span class="k">this</span><span class="o">-&gt;</span><span class="n">AICore</span><span class="p">()</span>
            <span class="p">.</span><span class="n">SetTiling</span><span class="p">(</span><span class="n">optiling</span><span class="o">::</span><span class="n">TilingFunc</span><span class="p">);</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">AICore</span><span class="p">().</span><span class="n">AddConfig</span><span class="p">(</span><span class="s">"ascend910b"</span><span class="p">);</span>

    <span class="p">}</span>
<span class="p">};</span>

<span class="n">OP_ADD</span><span class="p">(</span><span class="n">SubCustomTemplate</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="sub_template_kernelcpp">Sub_template_kernel.cpp</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"kernel_operator.h"</span><span class="cp">
#include</span> <span class="cpf">"sub_custom_template_tiling.h"</span><span class="cp">
#include</span> <span class="cpf">"kernel_operator_dump_tensor_intf_impl.h"</span><span class="cp">
</span><span class="k">constexpr</span> <span class="kt">int32_t</span> <span class="n">BUFFER_NUM</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>

<span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">TYPE_X</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">TYPE_Y</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">TYPE_Z</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">KernelSub</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="n">KernelSub</span><span class="p">()</span> <span class="p">{}</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Init</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">smallCoreDataNum</span><span class="p">,</span>
                                <span class="kt">uint32_t</span> <span class="n">bigCoreDataNum</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">finalBigTileNum</span><span class="p">,</span> 
                                <span class="kt">uint32_t</span> <span class="n">finalSmallTileNum</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">tileDataNum</span><span class="p">,</span> 
                                <span class="kt">uint32_t</span> <span class="n">smallTailDataNum</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">bigTailDataNum</span><span class="p">,</span> 
                                <span class="kt">uint32_t</span> <span class="n">tailBlockNum</span><span class="p">)</span> 
    <span class="p">{</span>
        <span class="kt">uint32_t</span> <span class="n">coreNum</span> <span class="o">=</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">();</span>
        <span class="kt">uint32_t</span> <span class="n">globalBufferIndex</span> <span class="o">=</span> <span class="n">bigCoreDataNum</span> <span class="o">*</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">();</span>

        <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileDataNum</span> <span class="o">=</span> <span class="n">tileDataNum</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">coreNum</span> <span class="o">&lt;</span> <span class="n">tailBlockNum</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">this</span><span class="o">-&gt;</span><span class="n">coreDataNum</span> <span class="o">=</span> <span class="n">bigCoreDataNum</span><span class="p">;</span>
          <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">=</span> <span class="n">finalBigTileNum</span><span class="p">;</span>
          <span class="k">this</span><span class="o">-&gt;</span><span class="n">tailDataNum</span> <span class="o">=</span> <span class="n">bigTailDataNum</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">else</span> <span class="p">{</span>
          <span class="k">this</span><span class="o">-&gt;</span><span class="n">coreDataNum</span> <span class="o">=</span> <span class="n">smallCoreDataNum</span><span class="p">;</span>
          <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">=</span> <span class="n">finalSmallTileNum</span><span class="p">;</span>
          <span class="k">this</span><span class="o">-&gt;</span><span class="n">tailDataNum</span> <span class="o">=</span> <span class="n">smallTailDataNum</span><span class="p">;</span>
          <span class="n">globalBufferIndex</span> <span class="o">-=</span> <span class="p">(</span><span class="n">bigCoreDataNum</span> <span class="o">-</span> <span class="n">smallCoreDataNum</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">()</span> <span class="o">-</span> <span class="n">tailBlockNum</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">xGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">TYPE_X</span><span class="o">*</span><span class="p">)</span><span class="n">x</span> <span class="o">+</span> <span class="n">globalBufferIndex</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">coreDataNum</span><span class="p">);</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">yGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">TYPE_Y</span><span class="o">*</span><span class="p">)</span><span class="n">y</span> <span class="o">+</span> <span class="n">globalBufferIndex</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">coreDataNum</span><span class="p">);</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">zGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">TYPE_Z</span><span class="o">*</span><span class="p">)</span><span class="n">z</span> <span class="o">+</span> <span class="n">globalBufferIndex</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">coreDataNum</span><span class="p">);</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">inQueueX</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileDataNum</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">TYPE_X</span><span class="p">));</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">inQueueY</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileDataNum</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">TYPE_Y</span><span class="p">));</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">outQueueZ</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileDataNum</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">TYPE_Z</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Process</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">int32_t</span> <span class="n">loopCount</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span><span class="p">;</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">processDataNum</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileDataNum</span><span class="p">;</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">loopCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="o">==</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
              <span class="k">this</span><span class="o">-&gt;</span><span class="n">processDataNum</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tailDataNum</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="n">CopyIn</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="n">Compute</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="n">CopyOut</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

<span class="nl">private:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">CopyIn</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">TYPE_X</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">TYPE_X</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">TYPE_Y</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">inQueueY</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">TYPE_Y</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">xLocal</span><span class="p">,</span> <span class="n">xGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileDataNum</span><span class="p">],</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">processDataNum</span><span class="p">);</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">yLocal</span><span class="p">,</span> <span class="n">yGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileDataNum</span><span class="p">],</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">processDataNum</span><span class="p">);</span>
        <span class="n">inQueueX</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
        <span class="n">inQueueY</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Compute</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
      <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">TYPE_X</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">TYPE_X</span><span class="o">&gt;</span><span class="p">();</span>
      <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">TYPE_Y</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">inQueueY</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">TYPE_Y</span><span class="o">&gt;</span><span class="p">();</span>
      <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">TYPE_Z</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="n">outQueueZ</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">TYPE_Z</span><span class="o">&gt;</span><span class="p">();</span>
      <span class="n">AscendC</span><span class="o">::</span><span class="n">Sub</span><span class="p">(</span><span class="n">zLocal</span><span class="p">,</span> <span class="n">xLocal</span><span class="p">,</span> <span class="n">yLocal</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">processDataNum</span><span class="p">);</span>
      <span class="n">outQueueZ</span><span class="p">.</span><span class="n">EnQue</span><span class="o">&lt;</span><span class="n">TYPE_Z</span><span class="o">&gt;</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>
      <span class="n">inQueueX</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
      <span class="n">inQueueY</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">CopyOut</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">TYPE_Z</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="n">outQueueZ</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">TYPE_Z</span><span class="o">&gt;</span><span class="p">();</span>  
      <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">zGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileDataNum</span><span class="p">],</span> <span class="n">zLocal</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">processDataNum</span><span class="p">);</span>
      <span class="n">outQueueZ</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>
    <span class="p">}</span>

<span class="nl">private:</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TPipe</span> <span class="n">pipe</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">QuePosition</span><span class="o">::</span><span class="n">VECIN</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">inQueueX</span><span class="p">,</span> <span class="n">inQueueY</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">QuePosition</span><span class="o">::</span><span class="n">VECOUT</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">outQueueZ</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="n">TYPE_X</span><span class="o">&gt;</span> <span class="n">xGm</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="n">TYPE_Y</span><span class="o">&gt;</span> <span class="n">yGm</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="n">TYPE_Z</span><span class="o">&gt;</span> <span class="n">zGm</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">coreDataNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileDataNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tailDataNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">processDataNum</span><span class="p">;</span>
<span class="p">};</span>

<span class="n">__global__</span> <span class="n">__aicore__</span> <span class="kt">void</span> <span class="n">sub_custom_template</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">workspace</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">tiling</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">REGISTER_TILING_DEFAULT</span><span class="p">(</span><span class="n">TilingDataTemplate</span><span class="p">);</span>
    <span class="n">GET_TILING_DATA_WITH_STRUCT</span><span class="p">(</span><span class="n">TilingDataTemplate</span><span class="p">,</span> <span class="n">tiling_data</span><span class="p">,</span> <span class="n">tiling</span><span class="p">);</span>
    <span class="n">KernelSub</span> <span class="n">op</span><span class="p">;</span>
    <span class="n">op</span><span class="p">.</span><span class="n">Init</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">,</span> <span class="n">tiling_data</span><span class="p">.</span><span class="n">smallCoreDataNum</span><span class="p">,</span> 
            <span class="n">tiling_data</span><span class="p">.</span><span class="n">bigCoreDataNum</span><span class="p">,</span> <span class="n">tiling_data</span><span class="p">.</span><span class="n">finalBigTileNum</span><span class="p">,</span> 
            <span class="n">tiling_data</span><span class="p">.</span><span class="n">finalSmallTileNum</span><span class="p">,</span> <span class="n">tiling_data</span><span class="p">.</span><span class="n">tileDataNum</span><span class="p">,</span> 
            <span class="n">tiling_data</span><span class="p">.</span><span class="n">smallTailDataNum</span><span class="p">,</span> <span class="n">tiling_data</span><span class="p">.</span><span class="n">bigTailDataNum</span><span class="p">,</span> 
            <span class="n">tiling_data</span><span class="p">.</span><span class="n">tailBlockNum</span><span class="p">);</span>
    <span class="n">op</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="subjson">Sub.json</h3>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[{</span><span class="w">
    </span><span class="nl">"op"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SubCustomTemplate"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"input_desc"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
            </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"x"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"param_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"required"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ND"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ND"</span><span class="p">],</span><span class="w">
            </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"float16"</span><span class="p">,</span><span class="w"> </span><span class="s2">"float"</span><span class="p">]</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"y"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"param_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"required"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ND"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ND"</span><span class="p">],</span><span class="w">
            </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"float16"</span><span class="p">,</span><span class="w"> </span><span class="s2">"float"</span><span class="p">]</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"output_desc"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"z"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"param_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"required"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ND"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ND"</span><span class="p">],</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"float16"</span><span class="p">,</span><span class="w"> </span><span class="s2">"float"</span><span class="p">]</span><span class="w">
    </span><span class="p">}]</span><span class="w">
</span><span class="p">}]</span><span class="w">
</span></code></pre></div></div>]]></content><author><name>溄</name><email>hongqiang.yan@qq.com</email></author><category term="技术博客" /><category term="CANN" /><category term="华为昇腾" /><category term="算子开发" /><category term="Tiling策略" /><category term="AscendC" /><category term="C++" /><summary type="html"><![CDATA[大小核动态负载均衡的减法算子 Tiling 策略实战。]]></summary></entry><entry><title type="html">C++ 宏定义的深度解析</title><link href="https://yhongq.github.io/posts/cpp-macros/" rel="alternate" type="text/html" title="C++ 宏定义的深度解析" /><published>2026-05-20T00:00:00+08:00</published><updated>2026-05-20T00:00:00+08:00</updated><id>https://yhongq.github.io/posts/cpp-macros</id><content type="html" xml:base="https://yhongq.github.io/posts/cpp-macros/"><![CDATA[<h1 id="c-宏定义的深度解析">C++ 宏定义的深度解析</h1>

<p>宏定义是 C++ 预处理器最强大的特性之一，但也是最容易滥用的。很多人对宏的理解停留在简单的文本替换层面，实际上宏的机制远比表面看起来复杂。</p>

<h2 id="宏的本质">宏的本质</h2>

<p>宏不是函数，不是变量，不是任何运行时的东西。它只是预处理器阶段的文本替换工具。这意味着宏在编译之前就已经完成了所有工作，编译器根本不知道宏的存在。</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define SQUARE(x) ((x) * (x))
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">SQUARE</span><span class="p">(</span><span class="n">a</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>  <span class="c1">// 展开为 ((a + 1) * (a + 1))</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里的关键是理解宏展开的时机。预处理器在编译之前进行文本替换，而编译器看到的是展开后的代码。这就是为什么宏的参数没有类型检查，也不会进行作用域查找。</p>

<h2 id="宏的陷阱">宏的陷阱</h2>

<h3 id="运算符优先级问题">运算符优先级问题</h3>

<p>最常见的错误是忽略括号：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define SQUARE(x) x * x  // 错误
#define SQUARE(x) ((x) * (x))  // 正确
</span>
<span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">SQUARE</span><span class="p">(</span><span class="mi">2</span> <span class="o">+</span> <span class="mi">3</span><span class="p">);</span>  <span class="c1">// 错误版本展开为 2 + 3 * 2 + 3 = 11</span>
</code></pre></div></div>

<p>但括号也不是万能的。考虑这个例子：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define DOUBLE(x) ((x) + (x))
</span>
<span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">DOUBLE</span><span class="p">(</span><span class="n">a</span><span class="o">++</span><span class="p">);</span>  <span class="c1">// 展开为 ((a++) + (a++))</span>
</code></pre></div></div>

<p>这会导致未定义行为，因为 <code class="language-plaintext highlighter-rouge">a++</code> 被执行了两次，且顺序不确定。</p>

<h3 id="宏参数的副作用">宏参数的副作用</h3>

<p>宏参数会被多次求值，这是宏最危险的地方：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define MAX(a, b) ((a) &gt; (b) ? (a) : (b))
</span>
<span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">max</span> <span class="o">=</span> <span class="n">MAX</span><span class="p">(</span><span class="n">x</span><span class="o">++</span><span class="p">,</span> <span class="n">y</span><span class="o">++</span><span class="p">);</span>  <span class="c1">// x 和 y 都会增加多次</span>
</code></pre></div></div>

<p>解决方案是使用 <code class="language-plaintext highlighter-rouge">__auto_type</code>（GCC 扩展）或者直接使用内联函数：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define MAX(a, b) ({ \
    __auto_type _a = (a); \
    __auto_type _b = (b); \
    _a &gt; _b ? _a : _b; \
})
</span></code></pre></div></div>

<h3 id="宏的作用域问题">宏的作用域问题</h3>

<p>宏没有作用域概念，一旦定义就全局有效：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">foo</span><span class="p">()</span> <span class="p">{</span>
    <span class="cp">#define LOCAL_MACRO 42
</span><span class="p">}</span>

<span class="kt">void</span> <span class="n">bar</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="n">LOCAL_MACRO</span><span class="p">;</span>  <span class="c1">// 仍然可用</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这可能导致意外的命名冲突。使用 <code class="language-plaintext highlighter-rouge">#undef</code> 可以取消宏定义，但这不是好习惯。</p>

<h2 id="宏的高级用法">宏的高级用法</h2>

<h3 id="字符串化操作符-">字符串化操作符 <code class="language-plaintext highlighter-rouge">#</code></h3>

<p><code class="language-plaintext highlighter-rouge">#</code> 操作符可以将宏参数转换为字符串：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define STRINGIFY(x) #x
</span>
<span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">str</span> <span class="o">=</span> <span class="n">STRINGIFY</span><span class="p">(</span><span class="n">hello</span><span class="p">);</span>  <span class="c1">// "hello"</span>
</code></pre></div></div>

<p>这个特性在日志和调试中很有用：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define LOG_VAR(x) printf("%s = %d\n", #x, (x))
</span>
<span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>
<span class="n">LOG_VAR</span><span class="p">(</span><span class="n">count</span><span class="p">);</span>  <span class="c1">// 输出: count = 42</span>
</code></pre></div></div>

<h3 id="连接操作符-">连接操作符 <code class="language-plaintext highlighter-rouge">##</code></h3>

<p><code class="language-plaintext highlighter-rouge">##</code> 操作符可以将两个标识符连接成一个新的标识符：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define MAKE_FUNC(name) func_##name
</span>
<span class="kt">void</span> <span class="nf">MAKE_FUNC</span><span class="p">(</span><span class="n">init</span><span class="p">)()</span> <span class="p">{</span>  <span class="c1">// 展开为 func_init()</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Initializing...</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这在生成重复代码时非常有用：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define DEFINE_GETTER(type, name) \
    type get_##name() const { return name; }
</span>
<span class="k">class</span> <span class="nc">Person</span> <span class="p">{</span>
<span class="nl">private:</span>
    <span class="kt">int</span> <span class="n">age</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">name</span><span class="p">;</span>
<span class="nl">public:</span>
    <span class="n">DEFINE_GETTER</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="n">age</span><span class="p">)</span>
    <span class="n">DEFINE_GETTER</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="条件编译">条件编译</h3>

<p>条件编译是宏最重要的用途之一：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifdef DEBUG
</span>    <span class="cp">#define LOG(msg) printf("[DEBUG] %s\n", msg)
#else
</span>    <span class="cp">#define LOG(msg) ((void)0)
#endif
</span>
<span class="n">LOG</span><span class="p">(</span><span class="s">"This is a debug message"</span><span class="p">);</span>
</code></pre></div></div>

<p>更现代的做法是使用 <code class="language-plaintext highlighter-rouge">constexpr</code> 和模板，但在某些情况下宏仍然是最佳选择。</p>

<h2 id="宏-vs-模板">宏 vs 模板</h2>

<p>很多人认为模板可以完全替代宏，这是错误的。宏和模板解决的问题不同：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 宏可以做模板做不到的事情</span>
<span class="cp">#define CONCAT(a, b) a##b
</span><span class="kt">int</span> <span class="n">CONCAT</span><span class="p">(</span><span class="n">var</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>  <span class="c1">// var1</span>

<span class="c1">// 模板可以做宏做不到的事情</span>
<span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="n">T</span> <span class="nf">add</span><span class="p">(</span><span class="n">T</span> <span class="n">a</span><span class="p">,</span> <span class="n">T</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>宏的优势在于：</p>
<ul>
  <li>可以操作标识符</li>
  <li>可以进行条件编译</li>
  <li>可以生成任意代码</li>
</ul>

<p>模板的优势在于：</p>
<ul>
  <li>类型安全</li>
  <li>作用域控制</li>
  <li>更好的调试信息</li>
</ul>

<h2 id="宏的最佳实践">宏的最佳实践</h2>

<h3 id="1-使用-do----while0-包装多语句宏">1. 使用 <code class="language-plaintext highlighter-rouge">do { ... } while(0)</code> 包装多语句宏</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define SAFE_DELETE(ptr) \
    do { \
        if (ptr) { \
            delete ptr; \
            ptr = nullptr; \
        } \
    } while(0)
</span>
<span class="k">if</span> <span class="p">(</span><span class="n">condition</span><span class="p">)</span>
    <span class="n">SAFE_DELETE</span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>  <span class="c1">// 正确工作</span>
<span class="k">else</span>
    <span class="nf">do_something</span><span class="p">();</span>
</code></pre></div></div>

<h3 id="2-避免在宏中使用复杂表达式">2. 避免在宏中使用复杂表达式</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 不好</span>
<span class="cp">#define COMPLEX_MACRO(x, y) \
    ((x) &gt; 0 ? ((x) * (y)) : ((x) + (y)))
</span>
<span class="c1">// 好</span>
<span class="cp">#define SIMPLE_MULTIPLY(x, y) ((x) * (y))
</span></code></pre></div></div>

<h3 id="3-使用大写命名宏">3. 使用大写命名宏</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define MAX_SIZE 1024  // 好
#define max_size 1024  // 不好
</span></code></pre></div></div>

<h3 id="4-考虑使用-constexpr-替代常量宏">4. 考虑使用 <code class="language-plaintext highlighter-rouge">constexpr</code> 替代常量宏</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 旧方式</span>
<span class="cp">#define PI 3.14159265358979323846
</span>
<span class="c1">// 新方式</span>
<span class="k">constexpr</span> <span class="kt">double</span> <span class="n">PI</span> <span class="o">=</span> <span class="mf">3.14159265358979323846</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="宏的实际应用场景">宏的实际应用场景</h2>

<h3 id="日志系统">日志系统</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define LOG(level, msg) \
    do { \
        if (level &gt;= LOG_LEVEL) { \
            printf("[%s] %s:%d: %s\n", \
                   #level, __FILE__, __LINE__, msg); \
        } \
    } while(0)
</span>
<span class="n">LOG</span><span class="p">(</span><span class="n">DEBUG</span><span class="p">,</span> <span class="s">"Starting application"</span><span class="p">);</span>
<span class="n">LOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">,</span> <span class="s">"Failed to open file"</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="单例模式">单例模式</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define SINGLETON(Class) \
public: \
    static Class&amp; getInstance() { \
        static Class instance; \
        return instance; \
    } \
    Class(const Class&amp;) = delete; \
    Class&amp; operator=(const Class&amp;) = delete;
</span>
<span class="k">class</span> <span class="nc">Database</span> <span class="p">{</span>
    <span class="n">SINGLETON</span><span class="p">(</span><span class="n">Database</span><span class="p">)</span>
<span class="nl">private:</span>
    <span class="n">Database</span><span class="p">()</span> <span class="o">=</span> <span class="k">default</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="性能优化">性能优化</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
</span>
<span class="k">if</span> <span class="p">(</span><span class="n">LIKELY</span><span class="p">(</span><span class="n">success</span><span class="p">))</span> <span class="p">{</span>
    <span class="c1">// 快速路径</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="c1">// 错误处理</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="总结">总结</h2>

<p>宏定义是 C++ 中一个强大的工具，但需要谨慎使用。理解宏的工作原理和潜在陷阱是写出高质量代码的关键。</p>

<p>记住：宏是文本替换，不是代码生成。每次使用宏时，都要思考它展开后的样子。</p>

<p>在现代 C++ 中，应该优先考虑 <code class="language-plaintext highlighter-rouge">constexpr</code>、模板和内联函数，只在必要时使用宏。但当你需要操作标识符、进行条件编译或者生成重复代码时，宏仍然是不可替代的工具。</p>]]></content><author><name>溄</name><email>hongqiang.yan@qq.com</email></author><category term="技术博客" /><category term="C++" /><category term="宏定义" /><category term="预处理器" /><summary type="html"><![CDATA[深入理解 C++ 宏定义与预处理器，剖析常见陷阱与最佳实践。]]></summary></entry><entry><title type="html">华为昇腾CANN算子工程化开发全指南：从零构建到部署实战</title><link href="https://yhongq.github.io/posts/cann-operator-engineering/" rel="alternate" type="text/html" title="华为昇腾CANN算子工程化开发全指南：从零构建到部署实战" /><published>2026-05-19T00:00:00+08:00</published><updated>2026-05-19T00:00:00+08:00</updated><id>https://yhongq.github.io/posts/cann-operator-engineering</id><content type="html" xml:base="https://yhongq.github.io/posts/cann-operator-engineering/"><![CDATA[<h1 id="华为昇腾cann算子工程化开发全指南从零构建到部署实战">华为昇腾CANN算子工程化开发全指南：从零构建到部署实战</h1>

<blockquote>
  <p>本文通过一个完整的向量减法算子工程，深入讲解华为昇腾CANN的算子工程化开发流程。从算子原型定义到工程创建，从Host侧Tiling机制到Kernel侧核函数实现，带你一步步掌握昇腾AI芯片的算子工程化开发范式。</p>
</blockquote>

<h2 id="1-背景与目标">1. 背景与目标</h2>

<p>在之前的算子开发中，我们主要关注核函数的实现，但在实际生产环境中，算子开发需要更完整的工程化体系。<strong>CANN算子工程</strong>提供了一套标准化的开发范式，涵盖了从Host侧的算子注册、形状推导、Tiling分块、任务下发与内存管理，到Kernel侧使用AscendC编写核函数计算逻辑的完整流程。</p>

<p>本文将以一个向量减法算子<code class="language-plaintext highlighter-rouge">SubCustomTemplate</code>为例，带你完整体验算子工程化开发的各个环节：</p>

<ul>
  <li>理解算子工程的整体架构和调用关系</li>
  <li>掌握算子原型定义和工程创建方法</li>
  <li>深入理解Tiling机制的作用和实现</li>
  <li>学习Host侧的Shape推导、Dtype推导和算子注册</li>
  <li>掌握Kernel侧的核函数实现和泛型编程</li>
  <li>了解工程编译部署和源码管理</li>
</ul>

<h2 id="2-算子工程整体架构">2. 算子工程整体架构</h2>

<h3 id="21-工程化开发-vs-独立核函数开发">2.1 工程化开发 vs 独立核函数开发</h3>

<table>
  <thead>
    <tr>
      <th>对比维度</th>
      <th>独立核函数开发</th>
      <th>工程化算子开发</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>开发范围</strong></td>
      <td>仅关注Kernel侧计算逻辑</td>
      <td>Host侧 + Kernel侧完整实现</td>
    </tr>
    <tr>
      <td><strong>内存管理</strong></td>
      <td>手动管理AscendCL接口</td>
      <td>框架自动管理内存分配</td>
    </tr>
    <tr>
      <td><strong>形状推导</strong></td>
      <td>需要手动处理</td>
      <td>框架自动推导和验证</td>
    </tr>
    <tr>
      <td><strong>动态Shape</strong></td>
      <td>支持有限</td>
      <td>完整支持动态Shape</td>
    </tr>
    <tr>
      <td><strong>算子注册</strong></td>
      <td>无需注册</td>
      <td>自动注册到框架</td>
    </tr>
    <tr>
      <td><strong>部署方式</strong></td>
      <td>手动编译和部署</td>
      <td>生成标准算子包</td>
    </tr>
    <tr>
      <td><strong>适用场景</strong></td>
      <td>学习和实验</td>
      <td>生产环境和算子库</td>
    </tr>
  </tbody>
</table>

<h3 id="22-算子工程目录结构">2.2 算子工程目录结构</h3>

<p>一个标准的CANN算子工程具有清晰的目录结构：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>custom_op/
├── CMakeLists.txt              # 主构建配置文件
├── CMakePresets.json          # CMake预设配置
├── build.sh                   # 编译脚本
├── framework/                 # 框架适配层
│   ├── CMakeLists.txt
│   └── tf_plugin/            # TensorFlow插件适配
│       ├── CMakeLists.txt
│       └── tensorflow_sub_custom_template_plugin.cc
├── op_host/                   # Host侧实现
│   ├── CMakeLists.txt
│   └── sub_custom_template.cpp  # 算子原型注册、Tiling、推导函数
├── op_kernel/                 # Kernel侧实现
│   ├── CMakeLists.txt
│   ├── sub_custom_template.cpp  # 核函数实现
│   └── sub_custom_template_tiling.h  # Tiling数据结构定义
└── build_out/                 # 编译输出目录
    ├── custom_opp_ubuntu_aarch64.run  # 最终算子安装包
    └── autogen/              # 自动生成的文件
</code></pre></div></div>

<h3 id="23-调用关系与数据流">2.3 调用关系与数据流</h3>

<p>算子工程的调用关系体现了清晰的分层架构：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────────┐
│                    应用层 (PyTorch/TF)                   │
└────────────────────┬────────────────────────────────────┘
                     │ 调用算子API
┌────────────────────▼────────────────────────────────────┐
│              算子API层 (aclnn_sub_custom_template)       │
│  - 参数验证                                              │
│  - 内存分配                                              │
│  - 任务下发                                              │
└────────────────────┬────────────────────────────────────┘
                     │ 传递Tiling数据
┌────────────────────▼────────────────────────────────────┐
│              Host侧 (op_host/sub_custom_template.cpp)    │
│  - 算子原型注册 (SubCustomTemplate)                      │
│  - Shape推导 (InferShape)                                │
│  - Dtype推导 (InferDataType)                             │
│  - Tiling计算 (TilingFunc)                               │
└────────────────────┬────────────────────────────────────┘
                     │ 启动核函数
┌────────────────────▼────────────────────────────────────┐
│           Kernel侧 (op_kernel/sub_custom_template.cpp)   │
│  - 核函数入口 (sub_custom_template)                      │
│  - 算子类实现 (KernelSub)                                │
│  - 流水线并行 (CopyIn/Compute/CopyOut)                   │
└─────────────────────────────────────────────────────────┘
</code></pre></div></div>

<h2 id="3-算子原型定义与工程创建">3. 算子原型定义与工程创建</h2>

<h3 id="31-算子原型定义">3.1 算子原型定义</h3>

<p>算子原型定义是工程创建的第一步，通过JSON文件描述算子的输入输出规格。以减法算子为例：</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"op"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SubCustomTemplate"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"input_desc"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"x"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"param_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"required"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ND"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ND"</span><span class="p">],</span><span class="w">
                </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"float16"</span><span class="p">,</span><span class="w"> </span><span class="s2">"float"</span><span class="p">]</span><span class="w">
            </span><span class="p">},</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"y"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"param_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"required"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ND"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ND"</span><span class="p">],</span><span class="w">
                </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"float16"</span><span class="p">,</span><span class="w"> </span><span class="s2">"float"</span><span class="p">]</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">],</span><span class="w">
        </span><span class="nl">"output_desc"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"z"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"param_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"required"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ND"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ND"</span><span class="p">],</span><span class="w">
                </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"float16"</span><span class="p">,</span><span class="w"> </span><span class="s2">"float"</span><span class="p">]</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<p><strong>关键字段说明：</strong></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">op</code>: 算子类型名称，必须唯一</li>
  <li><code class="language-plaintext highlighter-rouge">input_desc</code>: 输入张量描述数组
    <ul>
      <li><code class="language-plaintext highlighter-rouge">name</code>: 输入参数名称</li>
      <li><code class="language-plaintext highlighter-rouge">param_type</code>: 参数类型（required/optional）</li>
      <li><code class="language-plaintext highlighter-rouge">format</code>: 支持的数据格式（ND/NC1HWC0等）</li>
      <li><code class="language-plaintext highlighter-rouge">type</code>: 支持的数据类型（float16/float/int32等）</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">output_desc</code>: 输出张量描述数组，格式同输入</li>
</ul>

<h3 id="32-使用msopgen工具创建工程">3.2 使用msOpGen工具创建工程</h3>

<p>CANN提供了<code class="language-plaintext highlighter-rouge">msOpGen</code>工具来自动生成算子工程框架：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 安装msOpGen工具</span>
git clone https://gitee.com/ascend/msopgen.git
<span class="nb">cd </span>msopgen
python3 setup.py <span class="nb">install</span>

<span class="c"># 创建算子工程</span>
msopgen gen <span class="nt">-i</span> sub_custom_template.json <span class="nt">-c</span> custom_op <span class="nt">-t</span> AscendC910B
</code></pre></div></div>

<p>工具会自动生成以下内容：</p>

<ul>
  <li>完整的目录结构</li>
  <li>基础的CMakeLists.txt配置文件</li>
  <li>算子原型注册框架代码</li>
  <li>Kernel侧核函数框架代码</li>
  <li>编译脚本和配置文件</li>
</ul>

<h2 id="4-host侧实现详解">4. Host侧实现详解</h2>

<h3 id="41-命名空间与作用域">4.1 命名空间与作用域</h3>

<p>在Host侧实现中，C++的命名空间起到了重要作用，避免了全局命名冲突：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">namespace</span> <span class="n">optiling</span> <span class="p">{</span>
    <span class="c1">// Tiling函数实现</span>
    <span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">TilingFunc</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">TilingContext</span> <span class="o">*</span><span class="n">context</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 实现代码</span>
    <span class="p">}</span>
<span class="p">}</span>  <span class="c1">// namespace optiling</span>

<span class="k">namespace</span> <span class="n">ge</span> <span class="p">{</span>
    <span class="c1">// Shape和Dtype推导函数</span>
    <span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">InferShape</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">InferShapeContext</span><span class="o">*</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 实现代码</span>
    <span class="p">}</span>
<span class="p">}</span>  <span class="c1">// namespace ge</span>

<span class="k">namespace</span> <span class="n">ops</span> <span class="p">{</span>
    <span class="c1">// 算子原型注册</span>
    <span class="k">class</span> <span class="nc">SubCustomTemplate</span> <span class="o">:</span> <span class="k">public</span> <span class="n">OpDef</span> <span class="p">{</span>
        <span class="c1">// 实现代码</span>
    <span class="p">};</span>
<span class="p">}</span>  <span class="c1">// namespace ops</span>
</code></pre></div></div>

<p><strong>命名空间的作用：</strong></p>

<ol>
  <li><strong>optiling</strong>: 包含Tiling相关函数，避免与框架函数冲突</li>
  <li><strong>ge</strong>: 包含图引擎相关的推导函数</li>
  <li><strong>ops</strong>: 包含算子定义和注册</li>
</ol>

<h3 id="42-tiling机制详解">4.2 Tiling机制详解</h3>

<h4 id="421-什么是tiling">4.2.1 什么是Tiling？</h4>

<p><strong>Tiling</strong>是昇腾AI芯片编程中的核心概念，源于AI Core的片上存储容量限制。当输入数据量较大时，无法一次性加载所有数据到片上存储，因此需要将数据切分为多个小块，通过”搬运→计算→回写”的循环流水线完成全量计算。</p>

<h4 id="422-tiling的直观理解">4.2.2 Tiling的直观理解</h4>

<p>假设AI Core的片上存储容量为10个元素，需要计算长度为10的向量加法：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 片上存储容量
</span><span class="n">max_ub</span> <span class="o">=</span> <span class="mi">10</span>

<span class="c1"># 输入输出张量数量
</span><span class="n">input_output_num</span> <span class="o">=</span> <span class="mi">3</span>  <span class="c1"># A, B, C三个张量
</span>
<span class="c1"># 单次最大处理数据量
</span><span class="n">max_once_num</span> <span class="o">=</span> <span class="n">max_ub</span> <span class="o">//</span> <span class="n">input_output_num</span>  <span class="c1"># 3个元素
</span>
<span class="c1"># 需要的分块次数
</span><span class="n">tile_num</span> <span class="o">=</span> <span class="p">(</span><span class="mi">10</span> <span class="o">+</span> <span class="n">max_once_num</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">//</span> <span class="n">max_once_num</span>  <span class="c1"># 4次
</span></code></pre></div></div>

<p><strong>分块计算过程：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>原始数据: A = [1,2,3,4,5,6,7,8,9,10], B = [5,5,5,5,5,5,5,5,5,5]

Tile 0: A[0:3] + B[0:3] = [1,2,3] + [5,5,5] = [6,7,8]
Tile 1: A[3:6] + B[3:6] = [4,5,6] + [5,5,5] = [9,10,11]
Tile 2: A[6:9] + B[6:9] = [7,8,9] + [5,5,5] = [12,13,14]
Tile 3: A[9:10] + B[9:10] = [10] + [5] = [15]

最终结果: [6,7,8,9,10,11,12,13,14,15]
</code></pre></div></div>

<h4 id="423-tiling数据结构定义">4.2.3 Tiling数据结构定义</h4>

<p>首先定义Tiling数据结构，用于在Host和Kernel之间传递参数：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifndef SUB_CUSTOM_TEMPLATE_TILING_H
#define SUB_CUSTOM_TEMPLATE_TILING_H
#include</span> <span class="cpf">&lt;cstdint&gt;</span><span class="cp">
</span>
<span class="k">struct</span> <span class="nc">SubCustomTemplateTilingData</span> <span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">totalLength</span><span class="p">;</span>  <span class="c1">// 输入向量总长度</span>
    <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">;</span>      <span class="c1">// 分块数量</span>
<span class="p">};</span>

<span class="cp">#endif
</span></code></pre></div></div>

<p><strong>设计要点：</strong></p>

<ul>
  <li>使用<code class="language-plaintext highlighter-rouge">struct</code>而非<code class="language-plaintext highlighter-rouge">class</code>，默认public访问权限</li>
  <li>成员变量使用明确的数据类型（<code class="language-plaintext highlighter-rouge">uint32_t</code>）</li>
  <li>结构体名称以<code class="language-plaintext highlighter-rouge">TilingData</code>结尾，便于识别</li>
</ul>

<h4 id="424-tiling函数实现">4.2.4 Tiling函数实现</h4>

<p>Tiling函数在Host侧执行，负责计算分块参数：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">namespace</span> <span class="n">optiling</span> <span class="p">{</span>
    <span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">TilingFunc</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">TilingContext</span> <span class="o">*</span><span class="n">context</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 获取输入张量的总元素数量</span>
        <span class="kt">uint32_t</span> <span class="n">totalLength</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">GetOriginShape</span><span class="p">().</span><span class="n">GetShapeSize</span><span class="p">();</span>

        <span class="c1">// 设置使用的AI Core数量</span>
        <span class="n">context</span><span class="o">-&gt;</span><span class="n">SetBlockDim</span><span class="p">(</span><span class="mi">8</span><span class="p">);</span>

        <span class="c1">// 获取Tiling数据结构指针</span>
        <span class="n">SubCustomTemplateTilingData</span> <span class="o">*</span><span class="n">tiling</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetTilingData</span><span class="o">&lt;</span><span class="n">SubCustomTemplateTilingData</span><span class="o">&gt;</span><span class="p">();</span>

        <span class="c1">// 设置Tiling参数</span>
        <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">totalLength</span> <span class="o">=</span> <span class="n">totalLength</span><span class="p">;</span>
        <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>  <span class="c1">// 简单实现，实际可根据数据量动态计算</span>

        <span class="k">return</span> <span class="n">ge</span><span class="o">::</span><span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>  <span class="c1">// namespace optiling</span>
</code></pre></div></div>

<p><strong>关键步骤：</strong></p>

<ol>
  <li><strong>获取输入信息</strong>: 通过<code class="language-plaintext highlighter-rouge">context-&gt;GetInputShape(0)</code>获取第一个输入的形状信息</li>
  <li><strong>设置并行度</strong>: <code class="language-plaintext highlighter-rouge">SetBlockDim(8)</code>指定使用8个AI Core并行计算</li>
  <li><strong>填充Tiling数据</strong>: 将计算好的参数写入Tiling结构体</li>
  <li><strong>返回状态</strong>: 成功返回<code class="language-plaintext highlighter-rouge">GRAPH_SUCCESS</code></li>
</ol>

<h3 id="43-shape推导函数">4.3 Shape推导函数</h3>

<p>Shape推导函数根据输入张量的形状推断输出张量的形状：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">namespace</span> <span class="n">ge</span> <span class="p">{</span>
    <span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">InferShape</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">InferShapeContext</span><span class="o">*</span> <span class="n">context</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 获取第一个输入的形状</span>
        <span class="k">const</span> <span class="n">gert</span><span class="o">::</span><span class="n">Shape</span><span class="o">*</span> <span class="n">x1_shape</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>

        <span class="c1">// 获取输出张量的形状指针</span>
        <span class="n">gert</span><span class="o">::</span><span class="n">Shape</span><span class="o">*</span> <span class="n">y_shape</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetOutputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>

        <span class="c1">// 输出形状等于输入形状（减法算子特性）</span>
        <span class="o">*</span><span class="n">y_shape</span> <span class="o">=</span> <span class="o">*</span><span class="n">x1_shape</span><span class="p">;</span>

        <span class="k">return</span> <span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>作用与意义：</strong></p>

<ul>
  <li><strong>静态内存分配</strong>: 在构图阶段为输出张量预分配内存</li>
  <li><strong>形状验证</strong>: 确保输入输出形状匹配</li>
  <li><strong>广播支持</strong>: 可扩展支持不同形状的广播运算</li>
</ul>

<h3 id="44-dtype推导函数">4.4 Dtype推导函数</h3>

<p>Dtype推导函数根据输入数据类型推断输出数据类型：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">namespace</span> <span class="n">ge</span> <span class="p">{</span>
    <span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">InferDataType</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">InferDataTypeContext</span> <span class="o">*</span><span class="n">context</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 获取第一个输入的数据类型</span>
        <span class="k">const</span> <span class="k">auto</span> <span class="n">inputDataType</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputDataType</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>

        <span class="c1">// 输出数据类型等于输入数据类型</span>
        <span class="n">context</span><span class="o">-&gt;</span><span class="n">SetOutputDataType</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">inputDataType</span><span class="p">);</span>

        <span class="k">return</span> <span class="n">ge</span><span class="o">::</span><span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>设计考虑：</strong></p>

<ul>
  <li><strong>类型一致性</strong>: 减法算子保持输入输出类型一致</li>
  <li><strong>类型转换</strong>: 可扩展支持不同类型的自动转换</li>
  <li><strong>类型安全</strong>: 确保类型推导的正确性</li>
</ul>

<h3 id="45-算子原型注册">4.5 算子原型注册</h3>

<p>算子原型注册是将自定义算子注册到CANN框架的关键步骤：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">namespace</span> <span class="n">ops</span> <span class="p">{</span>
    <span class="k">class</span> <span class="nc">SubCustomTemplate</span> <span class="o">:</span> <span class="k">public</span> <span class="n">OpDef</span> <span class="p">{</span>
    <span class="nl">public:</span>
        <span class="k">explicit</span> <span class="n">SubCustomTemplate</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">)</span> <span class="o">:</span> <span class="n">OpDef</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="c1">// 注册输入张量x</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">Input</span><span class="p">(</span><span class="s">"x"</span><span class="p">)</span>
                <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
                <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT16</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
                <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
                <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>

            <span class="c1">// 注册输入张量y</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">Input</span><span class="p">(</span><span class="s">"y"</span><span class="p">)</span>
                <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
                <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT16</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
                <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
                <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>

            <span class="c1">// 注册输出张量z</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">Output</span><span class="p">(</span><span class="s">"z"</span><span class="p">)</span>
                <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
                <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT16</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
                <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
                <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>

            <span class="c1">// 设置推导函数</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">SetInferShape</span><span class="p">(</span><span class="n">ge</span><span class="o">::</span><span class="n">InferShape</span><span class="p">)</span>
                    <span class="p">.</span><span class="n">SetInferDataType</span><span class="p">(</span><span class="n">ge</span><span class="o">::</span><span class="n">InferDataType</span><span class="p">);</span>

            <span class="c1">// 设置Tiling函数和目标芯片</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">AICore</span><span class="p">()</span>
                <span class="p">.</span><span class="n">SetTiling</span><span class="p">(</span><span class="n">optiling</span><span class="o">::</span><span class="n">TilingFunc</span><span class="p">);</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">AICore</span><span class="p">().</span><span class="n">AddConfig</span><span class="p">(</span><span class="s">"ascend910b"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">};</span>

    <span class="c1">// 注册算子到框架</span>
    <span class="n">OP_ADD</span><span class="p">(</span><span class="n">SubCustomTemplate</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>注册流程：</strong></p>

<ol>
  <li><strong>继承OpDef</strong>: 自定义算子类继承<code class="language-plaintext highlighter-rouge">OpDef</code>基类</li>
  <li><strong>构造函数</strong>: 在构造函数中完成算子属性配置</li>
  <li><strong>输入输出注册</strong>: 使用<code class="language-plaintext highlighter-rouge">Input()</code>和<code class="language-plaintext highlighter-rouge">Output()</code>方法注册张量</li>
  <li><strong>函数绑定</strong>: 将推导函数和Tiling函数绑定到算子</li>
  <li><strong>算子注册</strong>: 使用<code class="language-plaintext highlighter-rouge">OP_ADD</code>宏将算子注册到框架</li>
</ol>

<h2 id="5-kernel侧实现详解">5. Kernel侧实现详解</h2>

<h3 id="51-c泛型编程的应用">5.1 C++泛型编程的应用</h3>

<p>Kernel侧大量使用了C++模板（泛型编程）来支持不同的数据类型：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span> <span class="o">&lt;</span><span class="k">class</span> <span class="nc">dtypeX</span><span class="p">,</span> <span class="k">class</span> <span class="nc">dtypeY</span><span class="p">,</span> <span class="k">class</span> <span class="nc">dtypeZ</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">KernelSub</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="n">KernelSub</span><span class="p">()</span> <span class="p">{}</span>

    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Init</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">,</span>
                                <span class="kt">uint32_t</span> <span class="n">totalLength</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 根据Tiling数据动态初始化</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">=</span> <span class="n">totalLength</span> <span class="o">/</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockNum</span><span class="p">();</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">=</span> <span class="n">tileNum</span><span class="p">;</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">/</span> <span class="n">tileNum</span> <span class="o">/</span> <span class="n">BUFFER_NUM</span><span class="p">;</span>

        <span class="c1">// 设置全局内存缓冲区</span>
        <span class="n">xGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">dtypeX</span> <span class="o">*</span><span class="p">)</span><span class="n">x</span> <span class="o">+</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">*</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span>
                           <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span><span class="p">);</span>
        <span class="n">yGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">dtypeY</span> <span class="o">*</span><span class="p">)</span><span class="n">y</span> <span class="o">+</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">*</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span>
                           <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span><span class="p">);</span>
        <span class="n">zGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">dtypeZ</span> <span class="o">*</span><span class="p">)</span><span class="n">z</span> <span class="o">+</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">*</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span>
                           <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span><span class="p">);</span>

        <span class="c1">// 初始化队列缓冲区</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">inQueueX</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dtypeX</span><span class="p">));</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">inQueueY</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dtypeY</span><span class="p">));</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">outQueueZ</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dtypeZ</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Process</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">int32_t</span> <span class="n">loopCount</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">*</span> <span class="n">BUFFER_NUM</span><span class="p">;</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">loopCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">CopyIn</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="n">Compute</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="n">CopyOut</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">printf</span><span class="p">(</span><span class="s">"Core %d executed %d times in total</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
                       <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span> <span class="n">loopCount</span><span class="p">);</span>
    <span class="p">}</span>
</code></pre></div></div>

<p><strong>泛型编程的优势：</strong></p>

<ol>
  <li><strong>类型安全</strong>: 编译时类型检查，避免运行时错误</li>
  <li><strong>代码复用</strong>: 一套代码支持多种数据类型</li>
  <li><strong>性能优化</strong>: 编译器针对具体类型进行优化</li>
  <li><strong>接口统一</strong>: 不同数据类型使用相同的接口</li>
</ol>

<h3 id="52-流水线并行实现">5.2 流水线并行实现</h3>

<h4 id="521-copyin---数据搬运">5.2.1 CopyIn - 数据搬运</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">private:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">CopyIn</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 从队列分配本地张量</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">inQueueY</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span><span class="p">();</span>

        <span class="c1">// 从全局内存拷贝数据到本地内存</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">xLocal</span><span class="p">,</span> <span class="n">xGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">],</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">yLocal</span><span class="p">,</span> <span class="n">yGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">],</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>

        <span class="c1">// 将本地张量加入队列</span>
        <span class="n">inQueueX</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
        <span class="n">inQueueY</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
    <span class="p">}</span>
</code></pre></div></div>

<p><strong>CopyIn的作用：</strong></p>

<ul>
  <li><strong>数据预取</strong>: 提前将数据搬运到本地内存</li>
  <li><strong>并行准备</strong>: 为计算单元准备数据</li>
  <li><strong>流水线优化</strong>: 与计算和输出重叠执行</li>
</ul>

<h4 id="522-compute---核心计算">5.2.2 Compute - 核心计算</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">Compute</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 从队列获取输入张量</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">inQueueY</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span><span class="p">();</span>

        <span class="c1">// 分配输出张量</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="n">outQueueZ</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span><span class="p">();</span>

        <span class="c1">// 执行减法计算</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">Sub</span><span class="p">(</span><span class="n">zLocal</span><span class="p">,</span> <span class="n">xLocal</span><span class="p">,</span> <span class="n">yLocal</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>

        <span class="c1">// 将输出张量加入队列</span>
        <span class="n">outQueueZ</span><span class="p">.</span><span class="n">EnQue</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>

        <span class="c1">// 释放输入张量</span>
        <span class="n">inQueueX</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
        <span class="n">inQueueY</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
    <span class="p">}</span>
</code></pre></div></div>

<p><strong>Compute的特点：</strong></p>

<ul>
  <li><strong>本地计算</strong>: 在片上存储中执行计算</li>
  <li><strong>向量化操作</strong>: 使用AscendC的向量化指令</li>
  <li><strong>内存管理</strong>: 及时释放不再使用的张量</li>
</ul>

<h4 id="523-copyout---结果输出">5.2.3 CopyOut - 结果输出</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">CopyOut</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 从队列获取输出张量</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="n">outQueueZ</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span><span class="p">();</span>

        <span class="c1">// 将结果拷贝回全局内存</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">zGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">],</span> <span class="n">zLocal</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>

        <span class="c1">// 释放输出张量</span>
        <span class="n">outQueueZ</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>
    <span class="p">}</span>
</code></pre></div></div>

<p><strong>CopyOut的作用：</strong></p>

<ul>
  <li><strong>结果回写</strong>: 将计算结果写回全局内存</li>
  <li><strong>内存释放</strong>: 及时释放本地内存资源</li>
  <li><strong>流水线衔接</strong>: 为下一轮计算腾出空间</li>
</ul>

<h3 id="53-核函数入口">5.3 核函数入口</h3>

<p>核函数是Kernel侧的入口点，负责初始化和调用算子类：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">extern</span> <span class="s">"C"</span> <span class="n">__global__</span> <span class="n">__aicore__</span> <span class="kt">void</span> <span class="nf">sub_custom_template</span><span class="p">(</span>
    <span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">workspace</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">tiling</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 注册Tiling数据结构</span>
    <span class="n">REGISTER_TILING_DEFAULT</span><span class="p">(</span><span class="n">SubCustomTemplateTilingData</span><span class="p">);</span>

    <span class="c1">// 获取Tiling数据</span>
    <span class="n">GET_TILING_DATA</span><span class="p">(</span><span class="n">tilingData</span><span class="p">,</span> <span class="n">tiling</span><span class="p">);</span>

    <span class="c1">// 实例化算子类</span>
    <span class="n">KernelSub</span><span class="o">&lt;</span><span class="n">DTYPE_X</span><span class="p">,</span> <span class="n">DTYPE_Y</span><span class="p">,</span> <span class="n">DTYPE_Z</span><span class="o">&gt;</span> <span class="n">op</span><span class="p">;</span>

    <span class="c1">// 初始化算子</span>
    <span class="n">op</span><span class="p">.</span><span class="n">Init</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">,</span> <span class="n">tilingData</span><span class="p">.</span><span class="n">totalLength</span><span class="p">,</span> <span class="n">tilingData</span><span class="p">.</span><span class="n">tileNum</span><span class="p">);</span>

    <span class="c1">// 执行计算</span>
    <span class="n">op</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>关键宏说明：</strong></p>

<ol>
  <li><strong>REGISTER_TILING_DEFAULT</strong>: 注册Tiling数据结构类型</li>
  <li><strong>GET_TILING_DATA</strong>: 从tiling参数中解析数据到结构体</li>
  <li><strong>extern “C”</strong>: 使用C链接方式，避免C++名称修饰</li>
</ol>

<h3 id="54-私有成员变量">5.4 私有成员变量</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">private:</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TPipe</span> <span class="n">pipe</span><span class="p">;</span>                                            <span class="c1">// 内存管理管道</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECIN</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">inQueueX</span><span class="p">;</span>  <span class="c1">// 输入队列X</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECIN</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">inQueueY</span><span class="p">;</span>  <span class="c1">// 输入队列Y</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECOUT</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">outQueueZ</span><span class="p">;</span> <span class="c1">// 输出队列Z</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span> <span class="n">xGm</span><span class="p">;</span>                              <span class="c1">// 全局张量X</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span> <span class="n">yGm</span><span class="p">;</span>                              <span class="c1">// 全局张量Y</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span> <span class="n">zGm</span><span class="p">;</span>                              <span class="c1">// 全局张量Z</span>
    <span class="kt">uint32_t</span> <span class="n">blockLength</span><span class="p">;</span>   <span class="c1">// 每个AI Core处理的数据长度</span>
    <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">;</span>       <span class="c1">// 分块数量</span>
    <span class="kt">uint32_t</span> <span class="n">tileLength</span><span class="p">;</span>    <span class="c1">// 每块的长度</span>
<span class="p">};</span>
</code></pre></div></div>

<p><strong>成员变量作用：</strong></p>

<ul>
  <li><strong>TPipe</strong>: 管理片上内存的分配和释放</li>
  <li><strong>TQue</strong>: 实现数据流的生产者-消费者模式</li>
  <li><strong>GlobalTensor</strong>: 管理全局内存中的张量</li>
  <li><strong>长度参数</strong>: 控制数据切分和并行策略</li>
</ul>

<h2 id="6-工程编译与部署">6. 工程编译与部署</h2>

<h3 id="61-编译配置">6.1 编译配置</h3>

<p>工程使用CMake构建系统，主要配置文件：</p>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cmake_minimum_required</span><span class="p">(</span>VERSION 3.16.0<span class="p">)</span>
<span class="nb">project</span><span class="p">(</span>opp<span class="p">)</span>
<span class="nb">find_package</span><span class="p">(</span>ASC REQUIRED<span class="p">)</span>
<span class="nb">set</span><span class="p">(</span>package_name <span class="si">${</span><span class="nv">vendor_name</span><span class="si">}</span><span class="p">)</span>

<span class="nf">npu_op_package</span><span class="p">(</span><span class="si">${</span><span class="nv">package_name</span><span class="si">}</span>
    TYPE RUN
    CONFIG
        INSTALL_PATH <span class="si">${</span><span class="nv">CMAKE_BINARY_DIR</span><span class="si">}</span>/
<span class="p">)</span>

<span class="nb">if</span><span class="p">(</span>EXISTS <span class="si">${</span><span class="nv">CMAKE_CURRENT_SOURCE_DIR</span><span class="si">}</span>/framework<span class="p">)</span>
    <span class="nb">add_subdirectory</span><span class="p">(</span>framework<span class="p">)</span>
<span class="nb">endif</span><span class="p">()</span>
<span class="nb">if</span><span class="p">(</span>EXISTS <span class="si">${</span><span class="nv">CMAKE_CURRENT_SOURCE_DIR</span><span class="si">}</span>/op_host<span class="p">)</span>
    <span class="nb">add_subdirectory</span><span class="p">(</span>op_host<span class="p">)</span>
<span class="nb">endif</span><span class="p">()</span>
<span class="nb">if</span><span class="p">(</span>EXISTS <span class="si">${</span><span class="nv">CMAKE_CURRENT_SOURCE_DIR</span><span class="si">}</span>/op_kernel<span class="p">)</span>
    <span class="nb">add_subdirectory</span><span class="p">(</span>op_kernel<span class="p">)</span>
<span class="nb">endif</span><span class="p">()</span>
</code></pre></div></div>

<h3 id="62-编译脚本">6.2 编译脚本</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$BASE_LIBS_PATH</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$ASCEND_HOME_PATH</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$ASCEND_AICPU_PATH</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
            </span><span class="nb">echo</span> <span class="s2">"please set env."</span>
            <span class="nb">exit </span>1
        <span class="k">else
            </span><span class="nb">export </span><span class="nv">ASCEND_HOME_PATH</span><span class="o">=</span><span class="nv">$ASCEND_AICPU_PATH</span>
        <span class="k">fi
    else
        </span><span class="nb">export </span><span class="nv">ASCEND_HOME_PATH</span><span class="o">=</span><span class="nv">$ASCEND_HOME_PATH</span>
    <span class="k">fi
else
    </span><span class="nb">export </span><span class="nv">ASCEND_HOME_PATH</span><span class="o">=</span><span class="nv">$BASE_LIBS_PATH</span>
<span class="k">fi

</span><span class="nv">script_path</span><span class="o">=</span><span class="si">$(</span><span class="nb">realpath</span> <span class="si">$(</span><span class="nb">dirname</span> <span class="nv">$0</span><span class="si">))</span>
<span class="nv">BUILD_DIR</span><span class="o">=</span><span class="s2">"build_out"</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> build_out
<span class="nb">rm</span> <span class="nt">-rf</span> build_out/<span class="k">*</span>

<span class="c"># 解析CMake预设</span>
<span class="nv">opts</span><span class="o">=</span><span class="si">$(</span>python3 <span class="nv">$ASCEND_HOME_PATH</span>/tools/tikcpp/ascendc_kernel_cmake/fwk_modules/util/preset_parse.py <span class="nv">$script_path</span>/CMakePresets.json<span class="si">)</span>

<span class="c"># 执行CMake构建</span>
cmake <span class="nt">-S</span> <span class="nb">.</span> <span class="nt">-B</span> <span class="s2">"</span><span class="nv">$BUILD_DIR</span><span class="s2">"</span> <span class="nt">--preset</span><span class="o">=</span>default
cmake <span class="nt">--build</span> <span class="s2">"</span><span class="nv">$BUILD_DIR</span><span class="s2">"</span> <span class="nt">--target</span> binary package <span class="nt">-j</span><span class="si">$(</span><span class="nb">nproc</span><span class="si">)</span>
</code></pre></div></div>

<h3 id="63-编译输出">6.3 编译输出</h3>

<p>编译成功后会生成算子安装包：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>build_out/
├── custom_opp_ubuntu_aarch64.run  # 算子安装包
└── autogen/
    ├── aclnn_sub_custom_template.cpp    # 自动生成的API接口
    ├── aclnn_sub_custom_template.h      # API头文件
    ├── op_proto.cc                       # 算子原型代码
    └── op_proto.h                        # 算子原型头文件
</code></pre></div></div>

<h3 id="64-部署安装">6.4 部署安装</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 安装算子包</span>
bash custom_opp_ubuntu_aarch64.run

<span class="c"># 验证安装</span>
<span class="nb">ls</span> /usr/local/Ascend/opp/vendors/customize/
</code></pre></div></div>

<h2 id="7-源码管理与最佳实践">7. 源码管理与最佳实践</h2>

<h3 id="71-代码组织原则">7.1 代码组织原则</h3>

<ol>
  <li><strong>模块化设计</strong>: Host侧和Kernel侧分离，职责清晰</li>
  <li><strong>命名规范</strong>: 文件名、函数名、变量名保持一致性</li>
  <li><strong>注释完整</strong>: 关键算法和接口添加详细注释</li>
  <li><strong>版本控制</strong>: 使用Git进行版本管理</li>
</ol>

<h3 id="72-c编程要点">7.2 C++编程要点</h3>

<h4 id="721-命名空间使用">7.2.1 命名空间使用</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 好的实践：使用命名空间避免冲突</span>
<span class="k">namespace</span> <span class="n">optiling</span> <span class="p">{</span>
    <span class="kt">void</span> <span class="n">TilingFunc</span><span class="p">()</span> <span class="p">{}</span>
<span class="p">}</span>

<span class="c1">// 避免：全局命名空间污染</span>
<span class="kt">void</span> <span class="n">TilingFunc</span><span class="p">()</span> <span class="p">{}</span>  <span class="c1">// 不推荐</span>
</code></pre></div></div>

<h4 id="722-const正确性">7.2.2 const正确性</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 好的实践：使用const保护不变数据</span>
<span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="nf">InferShape</span><span class="p">(</span><span class="k">const</span> <span class="n">gert</span><span class="o">::</span><span class="n">InferShapeContext</span><span class="o">*</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">gert</span><span class="o">::</span><span class="n">Shape</span><span class="o">*</span> <span class="n">inputShape</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="c1">// ...</span>
<span class="p">}</span>

<span class="c1">// 避免：忽略const修饰</span>
<span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">InferShape</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">InferShapeContext</span><span class="o">*</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">gert</span><span class="o">::</span><span class="n">Shape</span><span class="o">*</span> <span class="n">inputShape</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>  <span class="c1">// 不推荐</span>
    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="723-模板特化">7.2.3 模板特化</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 好的实践：为特定类型提供优化实现</span>
<span class="k">template</span> <span class="o">&lt;</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">KernelSub</span><span class="o">&lt;</span><span class="kt">float</span><span class="p">,</span> <span class="kt">float</span><span class="p">,</span> <span class="kt">float</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 针对float类型的特殊优化</span>
<span class="p">};</span>

<span class="c1">// 通用模板实现</span>
<span class="k">template</span> <span class="o">&lt;</span><span class="k">class</span> <span class="nc">dtypeX</span><span class="p">,</span> <span class="k">class</span> <span class="nc">dtypeY</span><span class="p">,</span> <span class="k">class</span> <span class="nc">dtypeZ</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">KernelSub</span> <span class="p">{</span>
    <span class="c1">// 通用实现</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="完整源码与实战命令">完整源码与实战命令</h2>

<h3 id="完整源码文件">完整源码文件</h3>

<h4 id="算子原型定义文件-sub_custom_templatejson">算子原型定义文件 (sub_custom_template.json)</h4>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[{</span><span class="w">
    </span><span class="nl">"op"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SubCustomTemplate"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"input_desc"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
            </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"x"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"param_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"required"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ND"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ND"</span><span class="p">],</span><span class="w">
            </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"float16"</span><span class="p">,</span><span class="w"> </span><span class="s2">"float"</span><span class="p">]</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"y"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"param_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"required"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ND"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ND"</span><span class="p">],</span><span class="w">
            </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"float16"</span><span class="p">,</span><span class="w"> </span><span class="s2">"float"</span><span class="p">]</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"output_desc"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"z"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"param_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"required"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ND"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ND"</span><span class="p">],</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"float16"</span><span class="p">,</span><span class="w"> </span><span class="s2">"float"</span><span class="p">]</span><span class="w">
    </span><span class="p">}]</span><span class="w">
</span><span class="p">}]</span><span class="w">
</span></code></pre></div></div>

<h4 id="912-tiling数据结构定义-op_kernelsub_custom_template_tilingh">9.1.2 Tiling数据结构定义 (op_kernel/sub_custom_template_tiling.h)</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifndef SUB_CUSTOM_TEMPLATE_TILING_H
#define SUB_CUSTOM_TEMPLATE_TILING_H
#include</span> <span class="cpf">&lt;cstdint&gt;</span><span class="cp">
</span>
<span class="k">struct</span> <span class="nc">SubCustomTemplateTilingData</span> <span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">totalLength</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">;</span>
<span class="p">};</span>

<span class="cp">#endif
</span></code></pre></div></div>

<h4 id="913-host侧实现-op_hostsub_custom_templatecpp">9.1.3 Host侧实现 (op_host/sub_custom_template.cpp)</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"../op_kernel/sub_custom_template_tiling.h"</span><span class="cp">
#include</span> <span class="cpf">"register/op_def_registry.h"</span><span class="cp">
</span>
<span class="k">namespace</span> <span class="n">optiling</span> <span class="p">{</span>
    <span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">TilingFunc</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">TilingContext</span> <span class="o">*</span><span class="n">context</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">uint32_t</span> <span class="n">totalLength</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">GetOriginShape</span><span class="p">().</span><span class="n">GetShapeSize</span><span class="p">();</span>
        <span class="n">context</span><span class="o">-&gt;</span><span class="n">SetBlockDim</span><span class="p">(</span><span class="mi">8</span><span class="p">);</span>
        <span class="n">SubCustomTemplateTilingData</span> <span class="o">*</span><span class="n">tiling</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetTilingData</span><span class="o">&lt;</span><span class="n">SubCustomTemplateTilingData</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">totalLength</span> <span class="o">=</span> <span class="n">totalLength</span><span class="p">;</span>
        <span class="n">tiling</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">ge</span><span class="o">::</span><span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">namespace</span> <span class="n">ge</span> <span class="p">{</span>
    <span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">InferShape</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">InferShapeContext</span><span class="o">*</span> <span class="n">context</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">const</span> <span class="n">gert</span><span class="o">::</span><span class="n">Shape</span><span class="o">*</span> <span class="n">x1_shape</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
        <span class="n">gert</span><span class="o">::</span><span class="n">Shape</span><span class="o">*</span> <span class="n">y_shape</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetOutputShape</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
        <span class="o">*</span><span class="n">y_shape</span> <span class="o">=</span> <span class="o">*</span><span class="n">x1_shape</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="k">static</span> <span class="n">ge</span><span class="o">::</span><span class="n">graphStatus</span> <span class="n">InferDataType</span><span class="p">(</span><span class="n">gert</span><span class="o">::</span><span class="n">InferDataTypeContext</span> <span class="o">*</span><span class="n">context</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">const</span> <span class="k">auto</span> <span class="n">inputDataType</span> <span class="o">=</span> <span class="n">context</span><span class="o">-&gt;</span><span class="n">GetInputDataType</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
        <span class="n">context</span><span class="o">-&gt;</span><span class="n">SetOutputDataType</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">inputDataType</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">ge</span><span class="o">::</span><span class="n">GRAPH_SUCCESS</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">namespace</span> <span class="n">ops</span> <span class="p">{</span>
    <span class="k">class</span> <span class="nc">SubCustomTemplate</span> <span class="o">:</span> <span class="k">public</span> <span class="n">OpDef</span> <span class="p">{</span>
    <span class="nl">public:</span>
        <span class="k">explicit</span> <span class="n">SubCustomTemplate</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">)</span> <span class="o">:</span> <span class="n">OpDef</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">Input</span><span class="p">(</span><span class="s">"x"</span><span class="p">)</span>
                <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
                <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT16</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
                <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
                <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">Input</span><span class="p">(</span><span class="s">"y"</span><span class="p">)</span>
                <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
                <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT16</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
                <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
                <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">Output</span><span class="p">(</span><span class="s">"z"</span><span class="p">)</span>
                <span class="p">.</span><span class="n">ParamType</span><span class="p">(</span><span class="n">REQUIRED</span><span class="p">)</span>
                <span class="p">.</span><span class="n">DataType</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT16</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">DT_FLOAT</span><span class="p">})</span>
                <span class="p">.</span><span class="n">Format</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">})</span>
                <span class="p">.</span><span class="n">UnknownShapeFormat</span><span class="p">({</span><span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">,</span> <span class="n">ge</span><span class="o">::</span><span class="n">FORMAT_ND</span><span class="p">});</span>

            <span class="k">this</span><span class="o">-&gt;</span><span class="n">SetInferShape</span><span class="p">(</span><span class="n">ge</span><span class="o">::</span><span class="n">InferShape</span><span class="p">).</span><span class="n">SetInferDataType</span><span class="p">(</span><span class="n">ge</span><span class="o">::</span><span class="n">InferDataType</span><span class="p">);</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">AICore</span><span class="p">().</span><span class="n">SetTiling</span><span class="p">(</span><span class="n">optiling</span><span class="o">::</span><span class="n">TilingFunc</span><span class="p">);</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">AICore</span><span class="p">().</span><span class="n">AddConfig</span><span class="p">(</span><span class="s">"ascend910b"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">};</span>
    <span class="n">OP_ADD</span><span class="p">(</span><span class="n">SubCustomTemplate</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="914-kernel侧实现-op_kernelsub_custom_templatecpp">9.1.4 Kernel侧实现 (op_kernel/sub_custom_template.cpp)</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"kernel_operator.h"</span><span class="cp">
#include</span> <span class="cpf">"sub_custom_template_tiling.h"</span><span class="cp">
#include</span> <span class="cpf">"kernel_operator_dump_tensor_intf_impl.h"</span><span class="cp">
</span>
<span class="k">constexpr</span> <span class="kt">int32_t</span> <span class="n">BUFFER_NUM</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>

<span class="k">template</span> <span class="o">&lt;</span><span class="k">class</span> <span class="nc">dtypeX</span><span class="p">,</span> <span class="k">class</span> <span class="nc">dtypeY</span><span class="p">,</span> <span class="k">class</span> <span class="nc">dtypeZ</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">KernelSub</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="n">KernelSub</span><span class="p">()</span> <span class="p">{}</span>
    
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Init</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">totalLength</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">=</span> <span class="n">totalLength</span> <span class="o">/</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockNum</span><span class="p">();</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">=</span> <span class="n">tileNum</span><span class="p">;</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">/</span> <span class="n">tileNum</span> <span class="o">/</span> <span class="n">BUFFER_NUM</span><span class="p">;</span>
        
        <span class="n">xGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">dtypeX</span> <span class="o">*</span><span class="p">)</span><span class="n">x</span> <span class="o">+</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">*</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span><span class="p">);</span>
        <span class="n">yGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">dtypeY</span> <span class="o">*</span><span class="p">)</span><span class="n">y</span> <span class="o">+</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">*</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span><span class="p">);</span>
        <span class="n">zGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="n">dtypeZ</span> <span class="o">*</span><span class="p">)</span><span class="n">z</span> <span class="o">+</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">*</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span><span class="p">);</span>
        
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">inQueueX</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dtypeX</span><span class="p">));</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">inQueueY</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dtypeY</span><span class="p">));</span>
        <span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="n">outQueueZ</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dtypeZ</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Process</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">int32_t</span> <span class="n">loopCount</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">*</span> <span class="n">BUFFER_NUM</span><span class="p">;</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">loopCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">CopyIn</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="n">Compute</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="n">CopyOut</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">printf</span><span class="p">(</span><span class="s">"Core %d executed %d times in total</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span> <span class="n">loopCount</span><span class="p">);</span>
    <span class="p">}</span>

<span class="nl">private:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">CopyIn</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">inQueueY</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">xLocal</span><span class="p">,</span> <span class="n">xGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">],</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">yLocal</span><span class="p">,</span> <span class="n">yGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">],</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>
        <span class="n">inQueueX</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
        <span class="n">inQueueY</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
    <span class="p">}</span>
    
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Compute</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">inQueueY</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="n">outQueueZ</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">Sub</span><span class="p">(</span><span class="n">zLocal</span><span class="p">,</span> <span class="n">xLocal</span><span class="p">,</span> <span class="n">yLocal</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>
        <span class="n">outQueueZ</span><span class="p">.</span><span class="n">EnQue</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>
        <span class="n">inQueueX</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
        <span class="n">inQueueY</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
    <span class="p">}</span>
    
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">CopyOut</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="n">outQueueZ</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">zGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">],</span> <span class="n">zLocal</span><span class="p">,</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>
        <span class="n">outQueueZ</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>
    <span class="p">}</span>

<span class="nl">private:</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TPipe</span> <span class="n">pipe</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECIN</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">inQueueX</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECIN</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">inQueueY</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECOUT</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">outQueueZ</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="n">dtypeX</span><span class="o">&gt;</span> <span class="n">xGm</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="n">dtypeY</span><span class="o">&gt;</span> <span class="n">yGm</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="n">dtypeZ</span><span class="o">&gt;</span> <span class="n">zGm</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">blockLength</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileLength</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">extern</span> <span class="s">"C"</span> <span class="n">__global__</span> <span class="n">__aicore__</span> <span class="kt">void</span> <span class="n">sub_custom_template</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">workspace</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">tiling</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">REGISTER_TILING_DEFAULT</span><span class="p">(</span><span class="n">SubCustomTemplateTilingData</span><span class="p">);</span>
    <span class="n">GET_TILING_DATA</span><span class="p">(</span><span class="n">tilingData</span><span class="p">,</span> <span class="n">tiling</span><span class="p">);</span>
    <span class="n">KernelSub</span><span class="o">&lt;</span><span class="n">DTYPE_X</span><span class="p">,</span> <span class="n">DTYPE_Y</span><span class="p">,</span> <span class="n">DTYPE_Z</span><span class="o">&gt;</span> <span class="n">op</span><span class="p">;</span>
    <span class="n">op</span><span class="p">.</span><span class="n">Init</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">,</span> <span class="n">tilingData</span><span class="p">.</span><span class="n">totalLength</span><span class="p">,</span> <span class="n">tilingData</span><span class="p">.</span><span class="n">tileNum</span><span class="p">);</span>
    <span class="n">op</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="915-测试程序-testmaincpp">9.1.5 测试程序 (test/main.cpp)</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;algorithm&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;cstdint&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;vector&gt;</span><span class="cp">
#include</span> <span class="cpf">"acl/acl.h"</span><span class="cp">
#include</span> <span class="cpf">"aclnn_sub_custom_template.h"</span><span class="cp">
</span>
<span class="cp">#define SUCCESS 0
#define FAILED 1
</span>
<span class="cp">#define CHECK_RET(cond, return_expr) \
    do {                             \
        if (!(cond)) {               \
            return_expr;             \
        }                            \
    } while (0)
</span>
<span class="cp">#define LOG_PRINT(message, ...)         \
    do {                                \
        printf(message, ##__VA_ARGS__); \
    } while (0)
</span>
<span class="kt">int64_t</span> <span class="nf">GetShapeSize</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int64_t</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">shape</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">int64_t</span> <span class="n">shapeSize</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">i</span> <span class="o">:</span> <span class="n">shape</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">shapeSize</span> <span class="o">*=</span> <span class="n">i</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">shapeSize</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="n">Init</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">deviceId</span><span class="p">,</span> <span class="n">aclrtStream</span> <span class="o">*</span><span class="n">stream</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">auto</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">aclInit</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">);</span>
    <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ACL_SUCCESS</span><span class="p">,</span> <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"aclInit failed. ERROR: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>
    <span class="n">ret</span> <span class="o">=</span> <span class="n">aclrtSetDevice</span><span class="p">(</span><span class="n">deviceId</span><span class="p">);</span>
    <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ACL_SUCCESS</span><span class="p">,</span> <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"aclrtSetDevice failed. ERROR: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>
    <span class="n">ret</span> <span class="o">=</span> <span class="n">aclrtCreateStream</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
    <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ACL_SUCCESS</span><span class="p">,</span> <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"aclrtCreateStream failed. ERROR: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">SUCCESS</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="kt">int</span> <span class="n">CreateAclTensor</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">hostData</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int64_t</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">shape</span><span class="p">,</span> <span class="kt">void</span> <span class="o">**</span><span class="n">deviceAddr</span><span class="p">,</span>
                    <span class="n">aclDataType</span> <span class="n">dataType</span><span class="p">,</span> <span class="n">aclTensor</span> <span class="o">**</span><span class="n">tensor</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">auto</span> <span class="n">size</span> <span class="o">=</span> <span class="n">GetShapeSize</span><span class="p">(</span><span class="n">shape</span><span class="p">)</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">T</span><span class="p">);</span>
    <span class="k">auto</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">aclrtMalloc</span><span class="p">(</span><span class="n">deviceAddr</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">ACL_MEM_MALLOC_HUGE_FIRST</span><span class="p">);</span>
    <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ACL_SUCCESS</span><span class="p">,</span> <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"aclrtMalloc failed. ERROR: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>
    <span class="n">ret</span> <span class="o">=</span> <span class="n">aclrtMemcpy</span><span class="p">(</span><span class="o">*</span><span class="n">deviceAddr</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">hostData</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">size</span><span class="p">,</span> <span class="n">ACL_MEMCPY_HOST_TO_DEVICE</span><span class="p">);</span>
    <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ACL_SUCCESS</span><span class="p">,</span> <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"aclrtMemcpy failed. ERROR: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>
    <span class="o">*</span><span class="n">tensor</span> <span class="o">=</span> <span class="n">aclCreateTensor</span><span class="p">(</span><span class="n">shape</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">shape</span><span class="p">.</span><span class="n">size</span><span class="p">(),</span> <span class="n">dataType</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">aclFormat</span><span class="o">::</span><span class="n">ACL_FORMAT_ND</span><span class="p">,</span> <span class="n">shape</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span>
                              <span class="n">shape</span><span class="p">.</span><span class="n">size</span><span class="p">(),</span> <span class="o">*</span><span class="n">deviceAddr</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">SUCCESS</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">DestroyResources</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">void</span> <span class="o">*&gt;</span> <span class="n">tensors</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">void</span> <span class="o">*&gt;</span> <span class="n">deviceAddrs</span><span class="p">,</span> <span class="n">aclrtStream</span> <span class="n">stream</span><span class="p">,</span>
                      <span class="kt">int32_t</span> <span class="n">deviceId</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">workspaceAddr</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">uint32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">tensors</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">tensors</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">!=</span> <span class="nb">nullptr</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">aclDestroyTensor</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">aclTensor</span> <span class="o">*&gt;</span><span class="p">(</span><span class="n">tensors</span><span class="p">[</span><span class="n">i</span><span class="p">]));</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">deviceAddrs</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">!=</span> <span class="nb">nullptr</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">aclrtFree</span><span class="p">(</span><span class="n">deviceAddrs</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">workspaceAddr</span> <span class="o">!=</span> <span class="nb">nullptr</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">aclrtFree</span><span class="p">(</span><span class="n">workspaceAddr</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="n">aclrtDestroyStream</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
    <span class="n">aclrtResetDevice</span><span class="p">(</span><span class="n">deviceId</span><span class="p">);</span>
    <span class="n">aclFinalize</span><span class="p">();</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="n">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">int32_t</span> <span class="n">deviceId</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">aclrtStream</span> <span class="n">stream</span><span class="p">;</span>
    <span class="k">auto</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">Init</span><span class="p">(</span><span class="n">deviceId</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">stream</span><span class="p">);</span>
    <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="mi">0</span><span class="p">,</span> <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"Init acl failed. ERROR: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>

    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int64_t</span><span class="o">&gt;</span> <span class="n">inputXShape</span> <span class="o">=</span> <span class="p">{</span><span class="mi">8</span><span class="p">,</span> <span class="mi">2048</span><span class="p">};</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int64_t</span><span class="o">&gt;</span> <span class="n">inputYShape</span> <span class="o">=</span> <span class="p">{</span><span class="mi">8</span><span class="p">,</span> <span class="mi">2048</span><span class="p">};</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int64_t</span><span class="o">&gt;</span> <span class="n">outputZShape</span> <span class="o">=</span> <span class="p">{</span><span class="mi">8</span><span class="p">,</span> <span class="mi">2048</span><span class="p">};</span>
    
    <span class="kt">void</span> <span class="o">*</span><span class="n">inputXDeviceAddr</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">inputYDeviceAddr</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">outputZDeviceAddr</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="n">aclTensor</span> <span class="o">*</span><span class="n">inputX</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="n">aclTensor</span> <span class="o">*</span><span class="n">inputY</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="n">aclTensor</span> <span class="o">*</span><span class="n">outputZ</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">aclFloat16</span><span class="o">&gt;</span> <span class="n">inputXHostData</span><span class="p">(</span><span class="n">inputXShape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">inputXShape</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">aclFloat16</span><span class="o">&gt;</span> <span class="n">inputYHostData</span><span class="p">(</span><span class="n">inputYShape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">inputYShape</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">aclFloat16</span><span class="o">&gt;</span> <span class="n">outputZHostData</span><span class="p">(</span><span class="n">outputZShape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">outputZShape</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
    
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">inputXShape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">inputXShape</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">inputXHostData</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">aclFloatToFloat16</span><span class="p">(</span><span class="mf">1.0</span><span class="p">);</span>
        <span class="n">inputYHostData</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">aclFloatToFloat16</span><span class="p">(</span><span class="mf">2.0</span><span class="p">);</span>
        <span class="n">outputZHostData</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">aclFloatToFloat16</span><span class="p">(</span><span class="mf">0.0</span><span class="p">);</span>
    <span class="p">}</span>
    
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">void</span> <span class="o">*&gt;</span> <span class="n">tensors</span> <span class="o">=</span> <span class="p">{</span><span class="n">inputX</span><span class="p">,</span> <span class="n">inputY</span><span class="p">,</span> <span class="n">outputZ</span><span class="p">};</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">void</span> <span class="o">*&gt;</span> <span class="n">deviceAddrs</span> <span class="o">=</span> <span class="p">{</span><span class="n">inputXDeviceAddr</span><span class="p">,</span> <span class="n">inputYDeviceAddr</span><span class="p">,</span> <span class="n">outputZDeviceAddr</span><span class="p">};</span>
    
    <span class="n">ret</span> <span class="o">=</span> <span class="n">CreateAclTensor</span><span class="p">(</span><span class="n">inputXHostData</span><span class="p">,</span> <span class="n">inputXShape</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">inputXDeviceAddr</span><span class="p">,</span> <span class="n">aclDataType</span><span class="o">::</span><span class="n">ACL_FLOAT16</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">inputX</span><span class="p">);</span>
    <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ACL_SUCCESS</span><span class="p">,</span> <span class="n">DestroyResources</span><span class="p">(</span><span class="n">tensors</span><span class="p">,</span> <span class="n">deviceAddrs</span><span class="p">,</span> <span class="n">stream</span><span class="p">,</span> <span class="n">deviceId</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>
    
    <span class="n">ret</span> <span class="o">=</span> <span class="n">CreateAclTensor</span><span class="p">(</span><span class="n">inputYHostData</span><span class="p">,</span> <span class="n">inputYShape</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">inputYDeviceAddr</span><span class="p">,</span> <span class="n">aclDataType</span><span class="o">::</span><span class="n">ACL_FLOAT16</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">inputY</span><span class="p">);</span>
    <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ACL_SUCCESS</span><span class="p">,</span> <span class="n">DestroyResources</span><span class="p">(</span><span class="n">tensors</span><span class="p">,</span> <span class="n">deviceAddrs</span><span class="p">,</span> <span class="n">stream</span><span class="p">,</span> <span class="n">deviceId</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>
    
    <span class="n">ret</span> <span class="o">=</span> <span class="n">CreateAclTensor</span><span class="p">(</span><span class="n">outputZHostData</span><span class="p">,</span> <span class="n">outputZShape</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">outputZDeviceAddr</span><span class="p">,</span> <span class="n">aclDataType</span><span class="o">::</span><span class="n">ACL_FLOAT16</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">outputZ</span><span class="p">);</span>
    <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ACL_SUCCESS</span><span class="p">,</span> <span class="n">DestroyResources</span><span class="p">(</span><span class="n">tensors</span><span class="p">,</span> <span class="n">deviceAddrs</span><span class="p">,</span> <span class="n">stream</span><span class="p">,</span> <span class="n">deviceId</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>

    <span class="kt">uint64_t</span> <span class="n">workspaceSize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">aclOpExecutor</span> <span class="o">*</span><span class="n">executor</span><span class="p">;</span>
    <span class="n">ret</span> <span class="o">=</span> <span class="n">aclnnSubCustomTemplateGetWorkspaceSize</span><span class="p">(</span><span class="n">inputX</span><span class="p">,</span> <span class="n">inputY</span><span class="p">,</span> <span class="n">outputZ</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">workspaceSize</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">executor</span><span class="p">);</span>
    <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ACL_SUCCESS</span><span class="p">,</span> <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"aclnnSubCustomTemplateGetWorkspaceSize failed. ERROR: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span>
              <span class="n">DestroyResources</span><span class="p">(</span><span class="n">tensors</span><span class="p">,</span> <span class="n">deviceAddrs</span><span class="p">,</span> <span class="n">stream</span><span class="p">,</span> <span class="n">deviceId</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>

    <span class="kt">void</span> <span class="o">*</span><span class="n">workspaceAddr</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">workspaceSize</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="n">aclrtMalloc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">workspaceAddr</span><span class="p">,</span> <span class="n">workspaceSize</span><span class="p">,</span> <span class="n">ACL_MEM_MALLOC_HUGE_FIRST</span><span class="p">);</span>
        <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ACL_SUCCESS</span><span class="p">,</span> <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"allocate workspace failed. ERROR: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span>
                  <span class="n">DestroyResources</span><span class="p">(</span><span class="n">tensors</span><span class="p">,</span> <span class="n">deviceAddrs</span><span class="p">,</span> <span class="n">stream</span><span class="p">,</span> <span class="n">deviceId</span><span class="p">,</span> <span class="n">workspaceAddr</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>
    <span class="p">}</span>
    
    <span class="n">ret</span> <span class="o">=</span> <span class="n">aclnnSubCustomTemplate</span><span class="p">(</span><span class="n">workspaceAddr</span><span class="p">,</span> <span class="n">workspaceSize</span><span class="p">,</span> <span class="n">executor</span><span class="p">,</span> <span class="n">stream</span><span class="p">);</span>
    <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ACL_SUCCESS</span><span class="p">,</span> <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"aclnnSubCustomTemplate failed. ERROR: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span>
              <span class="n">DestroyResources</span><span class="p">(</span><span class="n">tensors</span><span class="p">,</span> <span class="n">deviceAddrs</span><span class="p">,</span> <span class="n">stream</span><span class="p">,</span> <span class="n">deviceId</span><span class="p">,</span> <span class="n">workspaceAddr</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>

    <span class="n">ret</span> <span class="o">=</span> <span class="n">aclrtSynchronizeStream</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
    <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ACL_SUCCESS</span><span class="p">,</span> <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"aclrtSynchronizeStream failed. ERROR: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span>
              <span class="n">DestroyResources</span><span class="p">(</span><span class="n">tensors</span><span class="p">,</span> <span class="n">deviceAddrs</span><span class="p">,</span> <span class="n">stream</span><span class="p">,</span> <span class="n">deviceId</span><span class="p">,</span> <span class="n">workspaceAddr</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>

    <span class="k">auto</span> <span class="n">size</span> <span class="o">=</span> <span class="n">GetShapeSize</span><span class="p">(</span><span class="n">outputZShape</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">aclFloat16</span><span class="o">&gt;</span> <span class="n">resultData</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">ret</span> <span class="o">=</span> <span class="n">aclrtMemcpy</span><span class="p">(</span><span class="n">resultData</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">resultData</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">resultData</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span> <span class="n">outputZDeviceAddr</span><span class="p">,</span>
                      <span class="n">size</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">aclFloat16</span><span class="p">),</span> <span class="n">ACL_MEMCPY_DEVICE_TO_HOST</span><span class="p">);</span>
    <span class="n">CHECK_RET</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ACL_SUCCESS</span><span class="p">,</span> <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"copy result from device to host failed. ERROR: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span>
              <span class="n">DestroyResources</span><span class="p">(</span><span class="n">tensors</span><span class="p">,</span> <span class="n">deviceAddrs</span><span class="p">,</span> <span class="n">stream</span><span class="p">,</span> <span class="n">deviceId</span><span class="p">,</span> <span class="n">workspaceAddr</span><span class="p">);</span> <span class="k">return</span> <span class="n">FAILED</span><span class="p">);</span>

    <span class="n">DestroyResources</span><span class="p">(</span><span class="n">tensors</span><span class="p">,</span> <span class="n">deviceAddrs</span><span class="p">,</span> <span class="n">stream</span><span class="p">,</span> <span class="n">deviceId</span><span class="p">,</span> <span class="n">workspaceAddr</span><span class="p">);</span>

    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">aclFloat16</span><span class="o">&gt;</span> <span class="n">goldenData</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">aclFloatToFloat16</span><span class="p">(</span><span class="o">-</span><span class="mf">1.0</span><span class="p">));</span>
    <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"result is:</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int64_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"%.1f "</span><span class="p">,</span> <span class="n">aclFloat16ToFloat</span><span class="p">(</span><span class="n">resultData</span><span class="p">[</span><span class="n">i</span><span class="p">]));</span>
    <span class="p">}</span>
    <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    
    <span class="k">if</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">equal</span><span class="p">(</span><span class="n">resultData</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">resultData</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span> <span class="n">goldenData</span><span class="p">.</span><span class="n">begin</span><span class="p">()))</span> <span class="p">{</span>
        <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"test pass</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">LOG_PRINT</span><span class="p">(</span><span class="s">"test failed</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">FAILED</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>华为昇腾CANN算子工程化开发为我们提供了一套完整的开发范式，通过掌握这套技术，我们可以高效地开发出高性能的自定义算子，为AI应用提供强大的计算支撑。希望本文能够帮助读者快速入门CANN算子工程化开发，在实际项目中发挥重要作用。</p>]]></content><author><name>溄</name><email>hongqiang.yan@qq.com</email></author><category term="技术博客" /><category term="CANN" /><category term="华为昇腾" /><category term="算子开发" /><category term="AI" /><category term="AscendC" /><category term="C++" /><category term="工程化" /><summary type="html"><![CDATA[华为昇腾 CANN 算子工程化开发全指南：从零构建到部署上线的完整流程。]]></summary></entry><entry><title type="html">华为昇腾CANN算子开发实战：从Host到Device的Sigmoid激活函数算子练习</title><link href="https://yhongq.github.io/posts/cann-sigmoid/" rel="alternate" type="text/html" title="华为昇腾CANN算子开发实战：从Host到Device的Sigmoid激活函数算子练习" /><published>2026-05-19T00:00:00+08:00</published><updated>2026-05-19T00:00:00+08:00</updated><id>https://yhongq.github.io/posts/cann-sigmoid</id><content type="html" xml:base="https://yhongq.github.io/posts/cann-sigmoid/"><![CDATA[<h1 id="华为昇腾cann算子开发实战从host到device的sigmoid激活函数实现">华为昇腾CANN算子开发实战：从Host到Device的Sigmoid激活函数实现</h1>

<blockquote>
  <p>本文通过一个完整的Sigmoid激活函数算子（<code class="language-plaintext highlighter-rouge">y = sigmoid(x)</code>）代码，深入讲解基于华为昇腾CANN的算子开发流程。从AscendCL的Host侧内存管理，到AscendC的核函数与流水线并行设计，带你一步步理解昇腾AI芯片的编程模型。</p>
</blockquote>

<h2 id="1-背景与目标">1. 背景与目标</h2>

<p>在昇腾AI处理器的开发中，<strong>算子（Operator）</strong> 是计算的核心单元。CANN（Compute Architecture for Neural Networks）提供了AscendCL（Host侧）和AscendC（Device侧）两套编程接口。本文的目标是：</p>

<ul>
  <li>实现一个Sigmoid激活函数算子 <code class="language-plaintext highlighter-rouge">sigmoid_custom</code>，支持 <code class="language-plaintext highlighter-rouge">y[i] = 1 / (1 + exp(-x[i]))</code>。</li>
  <li>利用昇腾芯片的 <strong>AI Core</strong> 进行并行计算。</li>
  <li>采用 <strong>TPipe + TQue</strong> 实现流水线数据处理，提升吞吐。</li>
</ul>

<h2 id="2-整体架构概览">2. 整体架构概览</h2>

<p>代码结构上分为三个层级：</p>

<table>
  <thead>
    <tr>
      <th>层级</th>
      <th>文件/模块</th>
      <th>作用</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Host 主程序</td>
      <td><code class="language-plaintext highlighter-rouge">main()</code></td>
      <td>初始化环境，分配内存，调用核函数，验证结果</td>
    </tr>
    <tr>
      <td>Host 调用层</td>
      <td><code class="language-plaintext highlighter-rouge">kernel_sigmoid()</code></td>
      <td>封装AscendCL API，完成Host↔Device数据搬运</td>
    </tr>
    <tr>
      <td>Device 核函数</td>
      <td><code class="language-plaintext highlighter-rouge">sigmoid_custom</code> + <code class="language-plaintext highlighter-rouge">KernelSigmoid</code></td>
      <td>实际在AI Core上执行的并行计算逻辑</td>
    </tr>
  </tbody>
</table>

<p>整个流程如下：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host (CPU) → aclrtMalloc → 搬运数据 → 核函数启动 → 同步 → 回拷结果 → Host
                            ↘  Device (AI Core) 执行 sigmoid_custom
</code></pre></div></div>

<h2 id="3-host-侧ascendcl-环境与内存管理">3. Host 侧：AscendCL 环境与内存管理</h2>

<p>Host 侧的 <code class="language-plaintext highlighter-rouge">kernel_sigmoid</code> 函数完成了标准的 “初始化 → 分配 → 搬运 → 启动 → 同步 → 回拷 → 释放” 闭环。</p>

<p><strong>关键代码段：</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">aclInit</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">);</span>
<span class="n">aclrtSetDevice</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="n">aclrtCreateStream</span><span class="p">(</span><span class="o">&amp;</span><span class="n">stream</span><span class="p">);</span>

<span class="c1">// 分配Device内存</span>
<span class="n">aclrtMalloc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">xDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEM_MALLOC_HUGE_FIRST</span><span class="p">);</span>
<span class="n">aclrtMalloc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">yDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEM_MALLOC_HUGE_FIRST</span><span class="p">);</span>

<span class="c1">// 数据搬运 HOST → DEVICE</span>
<span class="n">aclrtMemcpy</span><span class="p">(</span><span class="n">xDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">xHost</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEMCPY_HOST_TO_DEVICE</span><span class="p">);</span>

<span class="c1">// 启动核函数</span>
<span class="n">sigmoid_custom</span><span class="o">&lt;&lt;&lt;</span><span class="n">blockDim</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="n">stream</span><span class="o">&gt;&gt;&gt;</span><span class="p">(</span><span class="n">xDevice</span><span class="p">,</span> <span class="n">yDevice</span><span class="p">,</span> <span class="n">tiling</span><span class="p">);</span>
<span class="n">aclrtSynchronizeStream</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>

<span class="c1">// 结果回拷 DEVICE → HOST</span>
<span class="n">aclrtMemcpy</span><span class="p">(</span><span class="n">yHost</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">yDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEMCPY_DEVICE_TO_HOST</span><span class="p">);</span>
</code></pre></div></div>

<blockquote>
  <p>💡 <strong>Tips</strong>：<code class="language-plaintext highlighter-rouge">aclrtSynchronizeStream</code> 是关键同步点，必须确保核函数完成后再回拷数据，否则结果错误。</p>
</blockquote>

<h2 id="4-device-侧ascendc-核函数与流水线设计">4. Device 侧：AscendC 核函数与流水线设计</h2>

<p>昇腾的核函数采用了 <strong>“多核并行 + 流水线分块”</strong> 的编程模型。</p>

<h3 id="41-多核数据切分">4.1 多核数据切分</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">=</span> <span class="n">totalLength</span> <span class="o">/</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockNum</span><span class="p">();</span>
</code></pre></div></div>

<p>每个核处理的数据范围由 <code class="language-plaintext highlighter-rouge">totalLength / blockDim</code> 确定，<code class="language-plaintext highlighter-rouge">GetBlockIdx()</code> 获取当前核的编号，实现无冲突的并行。</p>

<h3 id="42-流水线并行tpipe--tque">4.2 流水线并行（TPipe + TQue）</h3>

<p>核心思路：<strong>将数据搬运（CopyIn）、计算（Compute）、结果搬出（CopyOut）重叠进行</strong>，避免计算单元等待数据。</p>

<ul>
  <li><strong>TPipe</strong>：内存管理管道，负责管理 LocalMemory 中的缓冲区。</li>
  <li><strong>TQue</strong>：队列，承载数据流。我们使用一个 <strong>VECIN</strong> 队列（输入）和一个 <strong>VECOUT</strong> 队列（输出）。</li>
</ul>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">AscendC</span><span class="o">::</span><span class="n">TPipe</span> <span class="n">pipe</span><span class="p">;</span>
<span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECIN</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">inQueueX</span><span class="p">;</span>
<span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECOUT</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">outQueueY</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>三步循环流程：</strong></p>

<ol>
  <li><strong>CopyIn</strong>：从 GlobalMemory 搬运数据到 LocalTensor
    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
<span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">xLocal</span><span class="p">,</span> <span class="n">xGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="n">tileLength</span><span class="p">],</span> <span class="n">tileLength</span><span class="p">);</span>
<span class="n">inQueueX</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
</code></pre></div>    </div>
  </li>
  <li><strong>Compute</strong>：从队列取出数据，计算后放入输出队列
    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
<span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">outQueueY</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
<span class="n">AscendC</span><span class="o">::</span><span class="n">Sigmoid</span><span class="p">(</span><span class="n">yLocal</span><span class="p">,</span> <span class="n">xLocal</span><span class="p">,</span> <span class="n">tileLength</span><span class="p">);</span>
<span class="n">outQueueY</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
<span class="n">inQueueX</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
</code></pre></div>    </div>
  </li>
  <li><strong>CopyOut</strong>：将结果写回 GlobalMemory
    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="n">outQueueY</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
<span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">yGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="n">tileLength</span><span class="p">],</span> <span class="n">yLocal</span><span class="p">,</span> <span class="n">tileLength</span><span class="p">);</span>
<span class="n">outQueueY</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
</code></pre></div>    </div>
  </li>
</ol>

<p>这种设计使得数据搬运和计算可以<strong>流水线并行</strong>，极大提升硬件利用率。</p>

<h2 id="5-详细代码逐行解析">5. 详细代码逐行解析</h2>

<h3 id="51-自定义分块数据结构">5.1 自定义分块数据结构</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">SigmoidCustomTilingData</span> <span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">totalLength</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p>用于向核函数传递总长度和分块数量。</p>

<h3 id="52-核函数入口">5.2 核函数入口</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__global__</span> <span class="n">__aicore__</span> <span class="kt">void</span> <span class="nf">sigmoid_custom</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">SigmoidCustomTilingData</span> <span class="n">tiling</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">KERNEL_TASK_TYPE_DEFAULT</span><span class="p">(</span><span class="n">KERNEL_TYPE_AIV_ONLY</span><span class="p">);</span>
    <span class="n">KernelSigmoid</span> <span class="n">kernelSigmoid</span><span class="p">;</span>
    <span class="n">kernelSigmoid</span><span class="p">.</span><span class="n">Init</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">tiling</span><span class="p">.</span><span class="n">totalLength</span><span class="p">,</span> <span class="n">tiling</span><span class="p">.</span><span class="n">tileNum</span><span class="p">);</span>
    <span class="n">kernelSigmoid</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">KERNEL_TASK_TYPE_DEFAULT</code> 指定任务类型，<code class="language-plaintext highlighter-rouge">Init</code> 完成初始化，<code class="language-plaintext highlighter-rouge">Process</code> 启动流水线。</p>

<h3 id="53-验证与测试">5.3 验证与测试</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">golden</span> <span class="o">=</span> <span class="n">sigmoid</span><span class="p">(</span><span class="n">x</span><span class="p">);</span>
<span class="k">return</span> <span class="nf">VerifyResult</span><span class="p">(</span><span class="n">output</span><span class="p">,</span> <span class="n">golden</span><span class="p">);</span>
</code></pre></div></div>

<p>构造 Golden 结果（使用 CPU 实现）并对比，输出成功或失败信息。</p>

<h2 id="6-关键知识点总结">6. 关键知识点总结</h2>

<table>
  <thead>
    <tr>
      <th>概念</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>GM_ADDR</strong></td>
      <td>Global Memory 地址类型，由 Host 传入</td>
    </tr>
    <tr>
      <td><strong>LocalTensor</strong></td>
      <td>位于 LocalMemory 的片上数据块，计算核心操作对象</td>
    </tr>
    <tr>
      <td><strong>TPipe</strong></td>
      <td>管理片上缓冲区（LocalMemory）的生命周期</td>
    </tr>
    <tr>
      <td><strong>TQue</strong></td>
      <td>队列，支持 <code class="language-plaintext highlighter-rouge">AllocTensor</code> → <code class="language-plaintext highlighter-rouge">EnQue</code> → <code class="language-plaintext highlighter-rouge">DeQue</code> → <code class="language-plaintext highlighter-rouge">FreeTensor</code> 模式</td>
    </tr>
    <tr>
      <td><strong>DataCopy</strong></td>
      <td>在 GlobalMemory 和 LocalMemory 之间搬运数据</td>
    </tr>
    <tr>
      <td><strong>AscendC::Sigmoid</strong></td>
      <td>向量 Sigmoid 内置指令（SVE 向量化）</td>
    </tr>
  </tbody>
</table>

<hr />

<p><strong>参考文献</strong>：</p>
<ul>
  <li>华为昇腾 CANN 官方文档 (v6.3.0+)</li>
  <li>《Ascend C 编程指南》</li>
  <li>《AscendCL 接口参考》</li>
</ul>

<h2 id="完整代码">完整代码</h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>

<span class="cp">#include</span> <span class="cpf">&lt;cstdint&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;vector&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;cmath&gt;</span><span class="c1"> </span><span class="cp">
#include</span> <span class="cpf">&lt;algorithm&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;iterator&gt;</span><span class="cp">
#include</span> <span class="cpf">"acl/acl.h"</span><span class="cp">
#include</span> <span class="cpf">"kernel_operator.h"</span><span class="cp">
</span>
<span class="k">constexpr</span> <span class="kt">uint32_t</span> <span class="n">BUFFER_NUM</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="c1">// tensor num for each queue</span>

<span class="k">struct</span> <span class="nc">SigmoidCustomTilingData</span>
<span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">totalLength</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">;</span>
<span class="p">};</span>

<span class="c1">// 待补充……</span>
<span class="k">class</span> <span class="nc">KernelSigmoid</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="n">KernelSigmoid</span><span class="p">(){}</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Init</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">totalLength</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">=</span> <span class="n">totalLength</span> <span class="o">/</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockNum</span><span class="p">();</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">=</span> <span class="n">tileNum</span><span class="p">;</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">/</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">/</span> <span class="n">BUFFER_NUM</span><span class="p">;</span>

        <span class="n">xGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="kt">float</span> <span class="o">*</span><span class="p">)</span><span class="n">x</span> <span class="o">+</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">*</span><span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span><span class="p">);</span>
        <span class="n">yGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="kt">float</span> <span class="o">*</span><span class="p">)</span><span class="n">y</span> <span class="o">+</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">*</span><span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span><span class="p">);</span>

        <span class="k">this</span><span class="o">-&gt;</span><span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueX</span><span class="p">,</span><span class="n">BUFFER_NUM</span><span class="p">,</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">));</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">outQueueY</span><span class="p">,</span><span class="n">BUFFER_NUM</span><span class="p">,</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">));</span>

    <span class="p">}</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Process</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">// 待补充……</span>
        <span class="kt">int32_t</span> <span class="n">loopCount</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">*</span> <span class="n">BUFFER_NUM</span><span class="p">;</span>
        <span class="k">for</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">loopCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">CopyIn</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">Compute</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">CopyOut</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
        <span class="p">}</span>

    <span class="p">}</span>

<span class="nl">private:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">CopyIn</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 待补充……</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueX</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">xLocal</span><span class="p">,</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">xGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">],</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueX</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>


    <span class="p">}</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Compute</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 待补充……</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueX</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">outQueueY</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">Sigmoid</span><span class="p">(</span><span class="n">yLocal</span><span class="p">,</span><span class="n">xLocal</span><span class="p">,</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">outQueueY</span><span class="p">.</span><span class="n">EnQue</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueX</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>

    <span class="p">}</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">CopyOut</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 待补充……</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">outQueueY</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">yGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">],</span><span class="n">yLocal</span><span class="p">,</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">outQueueY</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
    <span class="p">}</span>

<span class="nl">private:</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TPipe</span> <span class="n">pipe</span><span class="p">;</span> <span class="c1">//TPipe内存管理对象</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECIN</span><span class="p">,</span><span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">inQueueX</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECOUT</span><span class="p">,</span><span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">outQueueY</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">xGm</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">yGm</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">blockLength</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileLength</span><span class="p">;</span>

<span class="p">};</span>

<span class="n">__global__</span> <span class="n">__aicore__</span> <span class="kt">void</span> <span class="n">sigmoid_custom</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">SigmoidCustomTilingData</span> <span class="n">tiling</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 待补充……</span>
    <span class="n">KERNEL_TASK_TYPE_DEFAULT</span><span class="p">(</span><span class="n">KERNEL_TYPE_AIV_ONLY</span><span class="p">);</span>
    <span class="n">KernelSigmoid</span> <span class="n">kernelSigmoid</span><span class="p">;</span>
    <span class="n">kernelSigmoid</span><span class="p">.</span><span class="n">Init</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">tiling</span><span class="p">.</span><span class="n">totalLength</span><span class="p">,</span> <span class="n">tiling</span><span class="p">.</span><span class="n">tileNum</span><span class="p">);</span>
    <span class="n">kernelSigmoid</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>



<span class="p">}</span>

<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">kernel_sigmoid</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">x</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 待补充……</span>
    <span class="k">constexpr</span> <span class="kt">uint32_t</span> <span class="n">blockDim</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">totalLength</span> <span class="o">=</span> <span class="n">x</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
    <span class="kt">size_t</span> <span class="n">totalByteSize</span> <span class="o">=</span> <span class="n">totalLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">);</span>
    <span class="kt">int32_t</span> <span class="n">deviceId</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">aclrtStream</span> <span class="n">stream</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="n">SigmoidCustomTilingData</span> <span class="n">tiling</span> <span class="o">=</span> <span class="p">{</span><span class="n">totalLength</span><span class="p">,</span><span class="mi">8</span><span class="p">};</span>

    <span class="c1">// HOST侧数据指针</span>
    <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">xHost</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uint8_t</span> <span class="o">*&gt;</span><span class="p">(</span><span class="n">x</span><span class="p">.</span><span class="n">data</span><span class="p">());</span>
    <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">yHost</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>

    <span class="c1">// DEVICE侧数据指针</span>
    <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">xDevice</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">yDevice</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>

    <span class="n">aclInit</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">);</span>
    <span class="n">aclrtSetDevice</span><span class="p">(</span><span class="n">deviceId</span><span class="p">);</span>
    <span class="n">aclrtCreateStream</span><span class="p">(</span><span class="o">&amp;</span><span class="n">stream</span><span class="p">);</span>

    <span class="n">aclrtMallocHost</span><span class="p">((</span><span class="kt">void</span> <span class="o">**</span><span class="p">)(</span><span class="o">&amp;</span><span class="n">yHost</span><span class="p">),</span><span class="n">totalByteSize</span><span class="p">);</span>
    <span class="c1">// DEVICE侧数据指针</span>
    <span class="n">aclrtMalloc</span><span class="p">((</span><span class="kt">void</span> <span class="o">**</span><span class="p">)(</span><span class="o">&amp;</span><span class="n">xDevice</span><span class="p">),</span><span class="n">totalByteSize</span><span class="p">,</span><span class="n">ACL_MEM_MALLOC_HUGE_FIRST</span> <span class="p">);</span>
    <span class="n">aclrtMalloc</span><span class="p">((</span><span class="kt">void</span> <span class="o">**</span><span class="p">)(</span><span class="o">&amp;</span><span class="n">yDevice</span><span class="p">),</span><span class="n">totalByteSize</span><span class="p">,</span><span class="n">ACL_MEM_MALLOC_HUGE_FIRST</span> <span class="p">);</span>

    <span class="n">aclrtMemcpy</span><span class="p">(</span><span class="n">xDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">xHost</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEMCPY_HOST_TO_DEVICE</span><span class="p">);</span>

    <span class="n">sigmoid_custom</span><span class="o">&lt;&lt;&lt;</span><span class="n">blockDim</span><span class="p">,</span><span class="nb">nullptr</span><span class="p">,</span><span class="n">stream</span><span class="o">&gt;&gt;&gt;</span><span class="p">(</span><span class="n">xDevice</span><span class="p">,</span><span class="n">yDevice</span><span class="p">,</span><span class="n">tiling</span><span class="p">);</span>
    <span class="n">aclrtSynchronizeStream</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>

    <span class="n">aclrtMemcpy</span><span class="p">(</span><span class="n">yHost</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">yDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEMCPY_DEVICE_TO_HOST</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">output</span><span class="p">((</span><span class="kt">float</span> <span class="o">*</span><span class="p">)</span><span class="n">yHost</span><span class="p">,</span> <span class="p">(</span><span class="kt">float</span> <span class="o">*</span><span class="p">)(</span><span class="n">yHost</span> <span class="o">+</span> <span class="n">totalByteSize</span><span class="p">));</span>

    <span class="n">aclrtFree</span><span class="p">((</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">xDevice</span><span class="p">);</span>
    <span class="n">aclrtFree</span><span class="p">((</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">yDevice</span><span class="p">);</span>
    <span class="n">aclrtFreeHost</span><span class="p">((</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">yHost</span><span class="p">);</span>
    <span class="n">aclrtDestroyStream</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
    <span class="n">aclrtResetDevice</span><span class="p">(</span><span class="n">deviceId</span><span class="p">);</span>

    <span class="n">aclFinalize</span><span class="p">();</span>

    <span class="k">return</span> <span class="n">output</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">uint32_t</span> <span class="n">VerifyResult</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">output</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">golden</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">auto</span> <span class="n">printTensor</span> <span class="o">=</span> <span class="p">[](</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">tensor</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">constexpr</span> <span class="kt">size_t</span> <span class="n">maxPrintSize</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">name</span> <span class="o">&lt;&lt;</span> <span class="s">": "</span><span class="p">;</span>
        <span class="n">std</span><span class="o">::</span><span class="n">copy</span><span class="p">(</span><span class="n">tensor</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">tensor</span><span class="p">.</span><span class="n">begin</span><span class="p">()</span> <span class="o">+</span> <span class="n">std</span><span class="o">::</span><span class="n">min</span><span class="p">(</span><span class="n">tensor</span><span class="p">.</span><span class="n">size</span><span class="p">(),</span> <span class="n">maxPrintSize</span><span class="p">),</span>
            <span class="n">std</span><span class="o">::</span><span class="n">ostream_iterator</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">cout</span><span class="p">,</span> <span class="s">" "</span><span class="p">));</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">tensor</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">&gt;</span> <span class="n">maxPrintSize</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"..."</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">};</span>
    <span class="n">printTensor</span><span class="p">(</span><span class="n">output</span><span class="p">,</span> <span class="s">"Output"</span><span class="p">);</span>
    <span class="n">printTensor</span><span class="p">(</span><span class="n">golden</span><span class="p">,</span> <span class="s">"Golden"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">equal</span><span class="p">(</span><span class="n">golden</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">golden</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span> <span class="n">output</span><span class="p">.</span><span class="n">begin</span><span class="p">()))</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"[Success] Case accuracy is verification passed."</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"[Failed] Case accuracy is verification failed!"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
        <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">sigmoid</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;&amp;</span> <span class="n">x</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">y</span><span class="p">(</span><span class="n">x</span><span class="p">.</span><span class="n">size</span><span class="p">());</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">x</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="mf">1.0</span><span class="n">f</span> <span class="o">/</span> <span class="p">(</span><span class="mf">1.0</span><span class="n">f</span> <span class="o">+</span> <span class="n">std</span><span class="o">::</span><span class="n">exp</span><span class="p">(</span><span class="o">-</span><span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">]));</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">y</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int32_t</span> <span class="n">main</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
    <span class="k">constexpr</span> <span class="kt">uint32_t</span> <span class="n">totalLength</span> <span class="o">=</span> <span class="mi">8</span> <span class="o">*</span> <span class="mi">2048</span><span class="p">;</span>
    <span class="k">constexpr</span> <span class="kt">float</span> <span class="n">valueX</span> <span class="o">=</span> <span class="mf">5.5</span><span class="n">f</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">x</span><span class="p">(</span><span class="n">totalLength</span><span class="p">,</span> <span class="n">valueX</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">output</span> <span class="o">=</span> <span class="n">kernel_sigmoid</span><span class="p">(</span><span class="n">x</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">golden</span> <span class="o">=</span> <span class="n">sigmoid</span><span class="p">(</span><span class="n">x</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">VerifyResult</span><span class="p">(</span><span class="n">output</span><span class="p">,</span> <span class="n">golden</span><span class="p">);</span>
<span class="p">}</span>

</code></pre></div></div>]]></content><author><name>溄</name><email>hongqiang.yan@qq.com</email></author><category term="技术博客" /><category term="CANN" /><category term="华为昇腾" /><category term="算子开发" /><category term="AI" /><category term="AscendC" /><category term="C++" /><summary type="html"><![CDATA[Sigmoid 激活函数算子的 Host 与 Device 端实现练习。]]></summary></entry><entry><title type="html">华为昇腾CANN算子开发实战：从Host到Device的向量减法实现</title><link href="https://yhongq.github.io/posts/cann-vector-sub/" rel="alternate" type="text/html" title="华为昇腾CANN算子开发实战：从Host到Device的向量减法实现" /><published>2026-05-19T00:00:00+08:00</published><updated>2026-05-19T00:00:00+08:00</updated><id>https://yhongq.github.io/posts/cann-vector-sub</id><content type="html" xml:base="https://yhongq.github.io/posts/cann-vector-sub/"><![CDATA[<h1 id="华为昇腾cann算子开发实战从host到device的向量减法实现">华为昇腾CANN算子开发实战：从Host到Device的向量减法实现</h1>

<blockquote>
  <p>本文通过一个完整的向量减法（<code class="language-plaintext highlighter-rouge">z = x - y</code>）算子代码，深入讲解基于华为昇腾CANN的算子开发流程。从AscendCL的Host侧内存管理，到AscendC的核函数与流水线并行设计，带你一步步理解昇腾AI芯片的编程模型。</p>
</blockquote>

<h2 id="1-背景与目标">1. 背景与目标</h2>

<p>在昇腾AI处理器的开发中，<strong>算子（Operator）</strong> 是计算的核心单元。CANN（Compute Architecture for Neural Networks）提供了AscendCL（Host侧）和AscendC（Device侧）两套编程接口。本文的目标是：</p>

<ul>
  <li>实现一个向量减法算子 <code class="language-plaintext highlighter-rouge">sub_custom</code>，支持 <code class="language-plaintext highlighter-rouge">z[i] = x[i] - y[i]</code>。</li>
  <li>利用昇腾芯片的 <strong>AI Core</strong> 进行并行计算。</li>
  <li>采用 <strong>TPipe + TQue</strong> 实现流水线数据处理，提升吞吐。</li>
</ul>

<h2 id="2-整体架构概览">2. 整体架构概览</h2>

<p>代码结构上分为三个层级：</p>

<table>
  <thead>
    <tr>
      <th>层级</th>
      <th>文件/模块</th>
      <th>作用</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Host 主程序</td>
      <td><code class="language-plaintext highlighter-rouge">main()</code></td>
      <td>初始化环境，分配内存，调用核函数，验证结果</td>
    </tr>
    <tr>
      <td>Host 调用层</td>
      <td><code class="language-plaintext highlighter-rouge">kernel_sub()</code></td>
      <td>封装AscendCL API，完成Host↔Device数据搬运</td>
    </tr>
    <tr>
      <td>Device 核函数</td>
      <td><code class="language-plaintext highlighter-rouge">sub_custom</code> + <code class="language-plaintext highlighter-rouge">KernelSub</code></td>
      <td>实际在AI Core上执行的并行计算逻辑</td>
    </tr>
  </tbody>
</table>

<p>整个流程如下：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host (CPU) → aclrtMalloc → 搬运数据 → 核函数启动 → 同步 → 回拷结果 → Host
                            ↘  Device (AI Core) 执行 sub_custom
</code></pre></div></div>

<h2 id="3-host-侧ascendcl-环境与内存管理">3. Host 侧：AscendCL 环境与内存管理</h2>

<p>Host 侧的 <code class="language-plaintext highlighter-rouge">kernel_sub</code> 函数完成了标准的 “初始化 → 分配 → 搬运 → 启动 → 同步 → 回拷 → 释放” 闭环。</p>

<p><strong>关键代码段：</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">aclInit</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">);</span>
<span class="n">aclrtSetDevice</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="n">aclrtCreateStream</span><span class="p">(</span><span class="o">&amp;</span><span class="n">stream</span><span class="p">);</span>

<span class="c1">// 分配Device内存</span>
<span class="n">aclrtMalloc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">xDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEM_MALLOC_HUGE_FIRST</span><span class="p">);</span>
<span class="n">aclrtMalloc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">yDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEM_MALLOC_HUGE_FIRST</span><span class="p">);</span>
<span class="n">aclrtMalloc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">zDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEM_MALLOC_HUGE_FIRST</span><span class="p">);</span>

<span class="c1">// 数据搬运 HOST → DEVICE</span>
<span class="n">aclrtMemcpy</span><span class="p">(</span><span class="n">xDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">xHost</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEMCPY_HOST_TO_DEVICE</span><span class="p">);</span>
<span class="n">aclrtMemcpy</span><span class="p">(</span><span class="n">yDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">yHost</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEMCPY_HOST_TO_DEVICE</span><span class="p">);</span>

<span class="c1">// 启动核函数</span>
<span class="n">sub_custom</span><span class="o">&lt;&lt;&lt;</span><span class="n">blockDim</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="n">stream</span><span class="o">&gt;&gt;&gt;</span><span class="p">(</span><span class="n">xDevice</span><span class="p">,</span> <span class="n">yDevice</span><span class="p">,</span> <span class="n">zDevice</span><span class="p">,</span> <span class="n">tiling</span><span class="p">);</span>
<span class="n">aclrtSynchronizeStream</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>

<span class="c1">// 结果回拷 DEVICE → HOST</span>
<span class="n">aclrtMemcpy</span><span class="p">(</span><span class="n">zHost</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">zDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEMCPY_DEVICE_TO_HOST</span><span class="p">);</span>
</code></pre></div></div>

<blockquote>
  <p>💡 <strong>Tips</strong>：<code class="language-plaintext highlighter-rouge">aclrtSynchronizeStream</code> 是关键同步点，必须确保核函数完成后再回拷数据，否则结果错误。</p>
</blockquote>

<h2 id="4-device-侧ascendc-核函数与流水线设计">4. Device 侧：AscendC 核函数与流水线设计</h2>

<p>昇腾的核函数采用了 <strong>“多核并行 + 流水线分块”</strong> 的编程模型。</p>

<h3 id="41-多核数据切分">4.1 多核数据切分</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">=</span> <span class="n">totalLength</span> <span class="o">/</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockNum</span><span class="p">();</span>
</code></pre></div></div>

<p>每个核处理的数据范围由 <code class="language-plaintext highlighter-rouge">totalLength / blockDim</code> 确定，<code class="language-plaintext highlighter-rouge">GetBlockIdx()</code> 获取当前核的编号，实现无冲突的并行。</p>

<h3 id="42-流水线并行tpipe--tque">4.2 流水线并行（TPipe + TQue）</h3>

<p>核心思路：<strong>将数据搬运（CopyIn）、计算（Compute）、结果搬出（CopyOut）重叠进行</strong>，避免计算单元等待数据。</p>

<ul>
  <li><strong>TPipe</strong>：内存管理管道，负责管理 LocalMemory 中的缓冲区。</li>
  <li><strong>TQue</strong>：队列，承载数据流。我们使用两个 <strong>VECIN</strong> 队列（输入）和一个 <strong>VECOUT</strong> 队列（输出）。</li>
</ul>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">AscendC</span><span class="o">::</span><span class="n">TPipe</span> <span class="n">pipe</span><span class="p">;</span>
<span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECIN</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">inQueueX</span><span class="p">,</span> <span class="n">inQueueY</span><span class="p">;</span>
<span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECOUT</span><span class="p">,</span> <span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">outQueueZ</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>三步循环流程：</strong></p>

<ol>
  <li><strong>CopyIn</strong>：从 GlobalMemory 搬运数据到 LocalTensor
    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
<span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">xLocal</span><span class="p">,</span> <span class="n">xGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="n">tileLength</span><span class="p">],</span> <span class="n">tileLength</span><span class="p">);</span>
<span class="n">inQueueX</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
</code></pre></div>    </div>
  </li>
  <li><strong>Compute</strong>：从队列取出数据，计算后放入输出队列
    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="n">inQueueX</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
<span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="n">outQueueZ</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
<span class="n">AscendC</span><span class="o">::</span><span class="n">Sub</span><span class="p">(</span><span class="n">zLocal</span><span class="p">,</span> <span class="n">xLocal</span><span class="p">,</span> <span class="n">yLocal</span><span class="p">,</span> <span class="n">tileLength</span><span class="p">);</span>
<span class="n">outQueueZ</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>
<span class="n">inQueueX</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
</code></pre></div>    </div>
  </li>
  <li><strong>CopyOut</strong>：将结果写回 GlobalMemory
    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="n">outQueueZ</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
<span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">zGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="n">tileLength</span><span class="p">],</span> <span class="n">zLocal</span><span class="p">,</span> <span class="n">tileLength</span><span class="p">);</span>
<span class="n">outQueueZ</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>
</code></pre></div>    </div>
  </li>
</ol>

<p>这种设计使得数据搬运和计算可以<strong>流水线并行</strong>，极大提升硬件利用率。</p>

<h2 id="5-详细代码逐行解析">5. 详细代码逐行解析</h2>

<h3 id="51-自定义分块数据结构">5.1 自定义分块数据结构</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">SubCustomTilingData</span> <span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">totalLength</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>用于向核函数传递总长度和分块数量。</p>

<h3 id="52-核函数入口">5.2 核函数入口</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__global__</span> <span class="n">__aicore__</span> <span class="kt">void</span> <span class="nf">sub_custom</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">,</span> <span class="n">SubCustomTilingData</span> <span class="n">tiling</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">KERNEL_TASK_TYPE_DEFAULT</span><span class="p">(</span><span class="n">KERNEL_TYPE_AIV_ONLY</span><span class="p">);</span>
    <span class="n">KernelSub</span> <span class="n">kernelSub</span><span class="p">;</span>
    <span class="n">kernelSub</span><span class="p">.</span><span class="n">Init</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">,</span> <span class="n">tiling</span><span class="p">.</span><span class="n">totalLength</span><span class="p">,</span> <span class="n">tiling</span><span class="p">.</span><span class="n">tileNum</span><span class="p">);</span>
    <span class="n">kernelSub</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">KERNEL_TASK_TYPE_DEFAULT</code> 指定任务类型，<code class="language-plaintext highlighter-rouge">Init</code> 完成初始化，<code class="language-plaintext highlighter-rouge">Process</code> 启动流水线。</p>

<h3 id="53-验证与测试">5.3 验证与测试</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">golden</span><span class="p">(</span><span class="n">totalLength</span><span class="p">,</span> <span class="n">valueX</span> <span class="o">-</span> <span class="n">valueY</span><span class="p">);</span>
<span class="k">return</span> <span class="nf">VerifyResult</span><span class="p">(</span><span class="n">output</span><span class="p">,</span> <span class="n">golden</span><span class="p">);</span>
</code></pre></div></div>
<p>构造 Golden 结果并对比，输出成功或失败信息。</p>

<h2 id="6-关键知识点总结">6. 关键知识点总结</h2>

<table>
  <thead>
    <tr>
      <th>概念</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>GM_ADDR</strong></td>
      <td>Global Memory 地址类型，由 Host 传入</td>
    </tr>
    <tr>
      <td><strong>LocalTensor</strong></td>
      <td>位于 LocalMemory 的片上数据块，计算核心操作对象</td>
    </tr>
    <tr>
      <td><strong>TPipe</strong></td>
      <td>管理片上缓冲区（LocalMemory）的生命周期</td>
    </tr>
    <tr>
      <td><strong>TQue</strong></td>
      <td>队列，支持 <code class="language-plaintext highlighter-rouge">AllocTensor</code> → <code class="language-plaintext highlighter-rouge">EnQue</code> → <code class="language-plaintext highlighter-rouge">DeQue</code> → <code class="language-plaintext highlighter-rouge">FreeTensor</code> 模式</td>
    </tr>
    <tr>
      <td><strong>DataCopy</strong></td>
      <td>在 GlobalMemory 和 LocalMemory 之间搬运数据</td>
    </tr>
    <tr>
      <td><strong>AscendC::Sub</strong></td>
      <td>向量减法内置指令（SVE 向量化）</td>
    </tr>
  </tbody>
</table>

<h2 id="7-完整代码">7. 完整代码</h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="cp">#include</span> <span class="cpf">&lt;cstdint&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;vector&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;algorithm&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;iterator&gt;</span><span class="cp">
#include</span> <span class="cpf">"acl/acl.h"</span><span class="cp">
#include</span> <span class="cpf">"kernel_operator.h"</span><span class="cp">
</span>
<span class="k">constexpr</span> <span class="kt">uint32_t</span> <span class="n">BUFFER_NUM</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="c1">// tensor num for each queue</span>

<span class="k">struct</span> <span class="nc">SubCustomTilingData</span>
<span class="p">{</span>
    <span class="kt">uint32_t</span> <span class="n">totalLength</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">class</span> <span class="nc">KernelSub</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="n">KernelSub</span><span class="p">(){}</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Init</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">totalLength</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 请补充……</span>

        <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">=</span> <span class="n">totalLength</span> <span class="o">/</span> <span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockNum</span><span class="p">();</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">=</span> <span class="n">tileNum</span><span class="p">;</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">/</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">/</span> <span class="n">BUFFER_NUM</span><span class="p">;</span>

        <span class="n">xGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="kt">float</span> <span class="o">*</span><span class="p">)</span><span class="n">x</span> <span class="o">+</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">*</span><span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span><span class="p">);</span>
        <span class="n">yGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="kt">float</span> <span class="o">*</span><span class="p">)</span><span class="n">y</span> <span class="o">+</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">*</span><span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span><span class="p">);</span>
        <span class="n">zGm</span><span class="p">.</span><span class="n">SetGlobalBuffer</span><span class="p">((</span><span class="n">__gm__</span> <span class="kt">float</span> <span class="o">*</span><span class="p">)</span><span class="n">z</span> <span class="o">+</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span> <span class="o">*</span><span class="n">AscendC</span><span class="o">::</span><span class="n">GetBlockIdx</span><span class="p">(),</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">blockLength</span><span class="p">);</span>

        <span class="k">this</span><span class="o">-&gt;</span><span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueX</span><span class="p">,</span><span class="n">BUFFER_NUM</span><span class="p">,</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">));</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueY</span><span class="p">,</span><span class="n">BUFFER_NUM</span><span class="p">,</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">));</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">pipe</span><span class="p">.</span><span class="n">InitBuffer</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">outQueueZ</span><span class="p">,</span><span class="n">BUFFER_NUM</span><span class="p">,</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Process</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">// 请补充……</span>
        <span class="kt">int32_t</span> <span class="n">loopCount</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileNum</span> <span class="o">*</span> <span class="n">BUFFER_NUM</span><span class="p">;</span>
        <span class="k">for</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">loopCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">CopyIn</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">Compute</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
            <span class="k">this</span><span class="o">-&gt;</span><span class="n">CopyOut</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

<span class="nl">private:</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">CopyIn</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 请补充……</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueX</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueY</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>

        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">xLocal</span><span class="p">,</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">xGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">],</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="n">yLocal</span><span class="p">,</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">yGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">],</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>

        <span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueX</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueY</span><span class="p">.</span><span class="n">EnQue</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">Compute</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 请补充……</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">xLocal</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueX</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">yLocal</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueY</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>

        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">outQueueZ</span><span class="p">.</span><span class="n">AllocTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>

        <span class="n">AscendC</span><span class="o">::</span><span class="n">Sub</span><span class="p">(</span><span class="n">zLocal</span><span class="p">,</span><span class="n">xLocal</span><span class="p">,</span><span class="n">yLocal</span><span class="p">,</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>

        <span class="k">this</span><span class="o">-&gt;</span><span class="n">outQueueZ</span><span class="p">.</span><span class="n">EnQue</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>

        <span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueX</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">xLocal</span><span class="p">);</span>
        <span class="k">this</span><span class="o">-&gt;</span><span class="n">inQueueY</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">yLocal</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="n">__aicore__</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">CopyOut</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// 请补充……</span>
        <span class="n">AscendC</span><span class="o">::</span><span class="n">LocalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">zLocal</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">outQueueZ</span><span class="p">.</span><span class="n">DeQue</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">();</span>

        <span class="n">AscendC</span><span class="o">::</span><span class="n">DataCopy</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">zGm</span><span class="p">[</span><span class="n">progress</span> <span class="o">*</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">],</span><span class="n">zLocal</span><span class="p">,</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">tileLength</span><span class="p">);</span>

        <span class="k">this</span><span class="o">-&gt;</span><span class="n">outQueueZ</span><span class="p">.</span><span class="n">FreeTensor</span><span class="p">(</span><span class="n">zLocal</span><span class="p">);</span>
    <span class="p">}</span>

<span class="nl">private:</span>
    <span class="c1">// 请补充……</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TPipe</span> <span class="n">pipe</span><span class="p">;</span> <span class="c1">//TPipe内存管理对象</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECIN</span><span class="p">,</span><span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">inQueueX</span><span class="p">,</span><span class="n">inQueueY</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">TQue</span><span class="o">&lt;</span><span class="n">AscendC</span><span class="o">::</span><span class="n">TPosition</span><span class="o">::</span><span class="n">VECOUT</span><span class="p">,</span><span class="n">BUFFER_NUM</span><span class="o">&gt;</span> <span class="n">outQueueZ</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">xGm</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">yGm</span><span class="p">;</span>
    <span class="n">AscendC</span><span class="o">::</span><span class="n">GlobalTensor</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">zGm</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">blockLength</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileNum</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">tileLength</span><span class="p">;</span>
<span class="p">};</span>

<span class="n">__global__</span> <span class="n">__aicore__</span> <span class="kt">void</span> <span class="n">sub_custom</span><span class="p">(</span><span class="n">GM_ADDR</span> <span class="n">x</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">y</span><span class="p">,</span> <span class="n">GM_ADDR</span> <span class="n">z</span><span class="p">,</span> <span class="n">SubCustomTilingData</span> <span class="n">tiling</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 请补充……</span>
    <span class="n">KERNEL_TASK_TYPE_DEFAULT</span><span class="p">(</span><span class="n">KERNEL_TYPE_AIV_ONLY</span><span class="p">);</span>
    <span class="n">KernelSub</span> <span class="n">kernelSub</span><span class="p">;</span>
    <span class="n">kernelSub</span><span class="p">.</span><span class="n">Init</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">,</span> <span class="n">tiling</span><span class="p">.</span><span class="n">totalLength</span><span class="p">,</span> <span class="n">tiling</span><span class="p">.</span><span class="n">tileNum</span><span class="p">);</span>
    <span class="n">kernelSub</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
<span class="p">}</span>

<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">kernel_sub</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">x</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">y</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 请补充……</span>
    <span class="k">constexpr</span> <span class="kt">uint32_t</span> <span class="n">blockDim</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">totalLength</span> <span class="o">=</span> <span class="n">x</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
    <span class="kt">size_t</span> <span class="n">totalByteSize</span> <span class="o">=</span> <span class="n">totalLength</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">float</span><span class="p">);</span>
    <span class="kt">int32_t</span> <span class="n">deviceId</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">aclrtStream</span> <span class="n">stream</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="n">SubCustomTilingData</span> <span class="n">tiling</span> <span class="o">=</span> <span class="p">{</span><span class="n">totalLength</span><span class="p">,</span> <span class="mi">8</span><span class="p">};</span>

    <span class="c1">// HOST侧数据指针</span>
    <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">xHost</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uint8_t</span> <span class="o">*&gt;</span><span class="p">(</span><span class="n">x</span><span class="p">.</span><span class="n">data</span><span class="p">());</span>
    <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">yHost</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uint8_t</span> <span class="o">*&gt;</span><span class="p">(</span><span class="n">y</span><span class="p">.</span><span class="n">data</span><span class="p">());</span>
    <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">zHost</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>

    <span class="c1">// DEVICE侧数据指针</span>
    <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">xDevice</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">yDevice</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">zDevice</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>

    <span class="n">aclInit</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">);</span>

    <span class="n">aclrtSetDevice</span><span class="p">(</span><span class="n">deviceId</span><span class="p">);</span>
    <span class="n">aclrtCreateStream</span><span class="p">(</span><span class="o">&amp;</span><span class="n">stream</span><span class="p">);</span> 


    <span class="n">aclrtMallocHost</span><span class="p">((</span><span class="kt">void</span> <span class="o">**</span><span class="p">)(</span><span class="o">&amp;</span><span class="n">zHost</span><span class="p">),</span><span class="n">totalByteSize</span><span class="p">);</span>
    <span class="c1">// DEVICE侧数据指针</span>
    <span class="n">aclrtMalloc</span><span class="p">((</span><span class="kt">void</span> <span class="o">**</span><span class="p">)(</span><span class="o">&amp;</span><span class="n">xDevice</span><span class="p">),</span><span class="n">totalByteSize</span><span class="p">,</span><span class="n">ACL_MEM_MALLOC_HUGE_FIRST</span> <span class="p">);</span>
    <span class="n">aclrtMalloc</span><span class="p">((</span><span class="kt">void</span> <span class="o">**</span><span class="p">)(</span><span class="o">&amp;</span><span class="n">yDevice</span><span class="p">),</span><span class="n">totalByteSize</span><span class="p">,</span><span class="n">ACL_MEM_MALLOC_HUGE_FIRST</span> <span class="p">);</span>
    <span class="n">aclrtMalloc</span><span class="p">((</span><span class="kt">void</span> <span class="o">**</span><span class="p">)(</span><span class="o">&amp;</span><span class="n">zDevice</span><span class="p">),</span><span class="n">totalByteSize</span><span class="p">,</span><span class="n">ACL_MEM_MALLOC_HUGE_FIRST</span> <span class="p">);</span>


    <span class="n">aclrtMemcpy</span><span class="p">(</span><span class="n">xDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">xHost</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEMCPY_HOST_TO_DEVICE</span><span class="p">);</span>
    <span class="n">aclrtMemcpy</span><span class="p">(</span><span class="n">yDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">yHost</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEMCPY_HOST_TO_DEVICE</span><span class="p">);</span>


    <span class="n">sub_custom</span><span class="o">&lt;&lt;&lt;</span><span class="n">blockDim</span><span class="p">,</span><span class="nb">nullptr</span><span class="p">,</span><span class="n">stream</span><span class="o">&gt;&gt;&gt;</span><span class="p">(</span><span class="n">xDevice</span><span class="p">,</span><span class="n">yDevice</span><span class="p">,</span><span class="n">zDevice</span><span class="p">,</span><span class="n">tiling</span><span class="p">);</span>
    <span class="n">aclrtSynchronizeStream</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>


    <span class="n">aclrtMemcpy</span><span class="p">(</span><span class="n">zHost</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">zDevice</span><span class="p">,</span> <span class="n">totalByteSize</span><span class="p">,</span> <span class="n">ACL_MEMCPY_DEVICE_TO_HOST</span><span class="p">);</span>

    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">output</span><span class="p">((</span><span class="kt">float</span> <span class="o">*</span><span class="p">)</span><span class="n">zHost</span><span class="p">,</span> <span class="p">(</span><span class="kt">float</span> <span class="o">*</span><span class="p">)(</span><span class="n">zHost</span> <span class="o">+</span> <span class="n">totalByteSize</span><span class="p">));</span>



    <span class="n">aclrtFree</span><span class="p">((</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">xDevice</span><span class="p">);</span>
    <span class="n">aclrtFree</span><span class="p">((</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">yDevice</span><span class="p">);</span>
    <span class="n">aclrtFree</span><span class="p">((</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">zDevice</span><span class="p">);</span>
    <span class="n">aclrtFreeHost</span><span class="p">((</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">zHost</span><span class="p">);</span>
    <span class="n">aclrtDestroyStream</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
    <span class="n">aclrtResetDevice</span><span class="p">(</span><span class="n">deviceId</span><span class="p">);</span>

    <span class="n">aclFinalize</span><span class="p">();</span>

    <span class="k">return</span> <span class="n">output</span><span class="p">;</span>

<span class="p">}</span>

<span class="kt">uint32_t</span> <span class="n">VerifyResult</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">output</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">golden</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">auto</span> <span class="n">printTensor</span> <span class="o">=</span> <span class="p">[](</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">tensor</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">constexpr</span> <span class="kt">size_t</span> <span class="n">maxPrintSize</span> <span class="o">=</span> <span class="mi">200</span><span class="p">;</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">name</span> <span class="o">&lt;&lt;</span> <span class="s">": "</span><span class="p">;</span>
        <span class="n">std</span><span class="o">::</span><span class="n">copy</span><span class="p">(</span><span class="n">tensor</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">tensor</span><span class="p">.</span><span class="n">begin</span><span class="p">()</span> <span class="o">+</span> <span class="n">std</span><span class="o">::</span><span class="n">min</span><span class="p">(</span><span class="n">tensor</span><span class="p">.</span><span class="n">size</span><span class="p">(),</span> <span class="n">maxPrintSize</span><span class="p">),</span>
            <span class="n">std</span><span class="o">::</span><span class="n">ostream_iterator</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">cout</span><span class="p">,</span> <span class="s">" "</span><span class="p">));</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">tensor</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">&gt;</span> <span class="n">maxPrintSize</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"..."</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="p">};</span>
    <span class="n">printTensor</span><span class="p">(</span><span class="n">output</span><span class="p">,</span> <span class="s">"Output"</span><span class="p">);</span>
    <span class="n">printTensor</span><span class="p">(</span><span class="n">golden</span><span class="p">,</span> <span class="s">"Golden"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">equal</span><span class="p">(</span><span class="n">golden</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">golden</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span> <span class="n">output</span><span class="p">.</span><span class="n">begin</span><span class="p">()))</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"[Success] Case accuracy is verification passed."</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"[Failed] Case accuracy is verification failed!"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
        <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int32_t</span> <span class="n">main</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
    <span class="k">constexpr</span> <span class="kt">uint32_t</span> <span class="n">totalLength</span> <span class="o">=</span> <span class="mi">8</span> <span class="o">*</span> <span class="mi">2048</span><span class="p">;</span>
    <span class="k">constexpr</span> <span class="kt">float</span> <span class="n">valueX</span> <span class="o">=</span> <span class="mf">1.2</span><span class="n">f</span><span class="p">;</span>
    <span class="k">constexpr</span> <span class="kt">float</span> <span class="n">valueY</span> <span class="o">=</span> <span class="mf">2.3</span><span class="n">f</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">x</span><span class="p">(</span><span class="n">totalLength</span><span class="p">,</span> <span class="n">valueX</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">y</span><span class="p">(</span><span class="n">totalLength</span><span class="p">,</span> <span class="n">valueY</span><span class="p">);</span>

    <span class="c1">// 请补充……</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">output</span> <span class="o">=</span> <span class="n">kernel_sub</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">);</span>

    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">float</span><span class="o">&gt;</span> <span class="n">golden</span><span class="p">(</span><span class="n">totalLength</span><span class="p">,</span> <span class="n">valueX</span> <span class="o">-</span> <span class="n">valueY</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">VerifyResult</span><span class="p">(</span><span class="n">output</span><span class="p">,</span> <span class="n">golden</span><span class="p">);</span>
<span class="p">}</span>

</code></pre></div></div>

<p><strong>参考文献</strong>：</p>
<ul>
  <li>华为昇腾 CANN 官方文档 (v6.3.0+)</li>
  <li>《Ascend C 编程指南》</li>
  <li>《AscendCL 接口参考》</li>
</ul>]]></content><author><name>溄</name><email>hongqiang.yan@qq.com</email></author><category term="技术博客" /><category term="CANN" /><category term="华为昇腾" /><category term="算子开发" /><category term="AI" /><category term="AscendC" /><category term="C++" /><summary type="html"><![CDATA[华为昇腾 CANN 向量减法算子开发实战：从 Host 到 Device 的完整实现流程。]]></summary></entry></feed>