[{"data":1,"prerenderedAt":2113},["ShallowReactive",2],{"post-\u002Fposts\u002Flock-free-concurrency-notes":3,"all-posts-nav":1835},{"id":4,"title":5,"body":6,"categories":1818,"date":1820,"description":1821,"draft":1822,"extension":1823,"hidden":1822,"meta":1824,"navigation":225,"path":1825,"published":1822,"seo":1826,"stem":1827,"tags":1828,"__hash__":1834},"posts\u002Fposts\u002Flock-free-concurrency-notes.md","无锁并发入门：从 CAS 到 Atomic Ring Buffer",{"type":7,"value":8,"toc":1804},"minimark",[9,18,23,26,29,52,55,179,186,193,196,333,336,340,343,346,354,364,367,373,383,387,390,431,434,488,494,497,511,514,520,523,527,530,536,539,620,631,654,657,663,667,676,679,835,838,976,979,996,1006,1010,1013,1020,1066,1069,1075,1078,1084,1087,1091,1094,1168,1179,1182,1262,1265,1277,1286,1289,1362,1376,1380,1383,1386,1392,1398,1401,1456,1459,1465,1468,1471,1475,1478,1481,1483,1523,1532,1535,1593,1596,1600,1603,1606,1612,1615,1621,1624,1627,1721,1724,1727,1730,1736,1739,1759,1762,1765,1777,1780,1797,1800],[10,11,12,13,17],"p",{},"这篇文章整理几个无锁并发里经常一起出现的概念：CAS、busy polling、atomic ring buffer、release\u002Facquire、store buffer 和 CPU cache warmup。它们看起来分散，其实是一条完整链路：",[14,15,16],"strong",{},"硬件提供原子操作，程序用原子变量做同步，线程用轮询降低延迟，最后还要理解 cache 和内存模型带来的性能与可见性问题","。",[19,20,22],"h2",{"id":21},"cas无锁编程的基本原子操作","CAS：无锁编程的基本原子操作",[10,24,25],{},"CAS 是 Compare-And-Swap 的缩写，可以理解成一个硬件保证原子的「比较并交换」操作。",[10,27,28],{},"它有三个参数：",[30,31,32,40,46],"ul",{},[33,34,35,39],"li",{},[36,37,38],"code",{},"V","：要修改的内存地址",[33,41,42,45],{},[36,43,44],{},"A","：期望旧值",[33,47,48,51],{},[36,49,50],{},"B","：准备写入的新值",[10,53,54],{},"伪代码如下：",[56,57,62],"pre",{"className":58,"code":59,"language":60,"meta":61,"style":61},"language-cpp shiki shiki-themes github-dark-dimmed github-light","bool CAS(addr V, value A, value B) {\n    if (*V == A) {\n        *V = B;\n        return true;\n    }\n    return false;\n}\n","cpp","",[36,63,64,108,129,143,156,162,173],{"__ignoreMap":61},[65,66,69,73,77,81,85,89,92,95,98,100,102,105],"span",{"class":67,"line":68},"line",1,[65,70,72],{"class":71},"s6PUj","bool",[65,74,76],{"class":75},"saVmf"," CAS",[65,78,80],{"class":79},"ssh_m","(",[65,82,84],{"class":83},"sqRhv","addr",[65,86,88],{"class":87},"sNjOc"," V",[65,90,91],{"class":79},", ",[65,93,94],{"class":83},"value",[65,96,97],{"class":87}," A",[65,99,91],{"class":79},[65,101,94],{"class":83},[65,103,104],{"class":87}," B",[65,106,107],{"class":79},") {\n",[65,109,111,114,117,120,123,126],{"class":67,"line":110},2,[65,112,113],{"class":71},"    if",[65,115,116],{"class":79}," (",[65,118,119],{"class":71},"*",[65,121,122],{"class":79},"V ",[65,124,125],{"class":71},"==",[65,127,128],{"class":79}," A) {\n",[65,130,132,135,137,140],{"class":67,"line":131},3,[65,133,134],{"class":71},"        *",[65,136,122],{"class":79},[65,138,139],{"class":71},"=",[65,141,142],{"class":79}," B;\n",[65,144,146,149,153],{"class":67,"line":145},4,[65,147,148],{"class":71},"        return",[65,150,152],{"class":151},"swcJU"," true",[65,154,155],{"class":79},";\n",[65,157,159],{"class":67,"line":158},5,[65,160,161],{"class":79},"    }\n",[65,163,165,168,171],{"class":67,"line":164},6,[65,166,167],{"class":71},"    return",[65,169,170],{"class":151}," false",[65,172,155],{"class":79},[65,174,176],{"class":67,"line":175},7,[65,177,178],{"class":79},"}\n",[10,180,181,182,185],{},"关键点是：",[14,183,184],{},"读旧值、比较、写新值这三个动作整体不可分割","。如果多个线程同时执行，硬件保证同一时刻只有一个 CAS 能成功。",[10,187,188,189,192],{},"在 x86 上，CAS 通常对应 ",[36,190,191],{},"LOCK CMPXCHG"," 指令；在 ARM 上，常见实现是 LL\u002FSC 语义，也就是 Load-Linked \u002F Store-Conditional。",[10,194,195],{},"一个典型例子是无锁计数器：",[56,197,199],{"className":58,"code":198,"language":60,"meta":61,"style":61},"std::atomic\u003Cint> counter{0};\n\nvoid increment() {\n    int old_value;\n    int new_value;\n\n    do {\n        old_value = counter.load(std::memory_order_relaxed);\n        new_value = old_value + 1;\n    } while (!counter.compare_exchange_weak(old_value, new_value));\n}\n",[36,200,201,221,227,238,246,253,257,265,286,305,328],{"__ignoreMap":61},[65,202,203,206,209,212,215,218],{"class":67,"line":68},[65,204,205],{"class":83},"std",[65,207,208],{"class":79},"::atomic",[65,210,211],{"class":71},"\u003Cint>",[65,213,214],{"class":79}," counter{",[65,216,217],{"class":151},"0",[65,219,220],{"class":79},"};\n",[65,222,223],{"class":67,"line":110},[65,224,226],{"emptyLinePlaceholder":225},true,"\n",[65,228,229,232,235],{"class":67,"line":131},[65,230,231],{"class":71},"void",[65,233,234],{"class":75}," increment",[65,236,237],{"class":79},"() {\n",[65,239,240,243],{"class":67,"line":145},[65,241,242],{"class":71},"    int",[65,244,245],{"class":79}," old_value;\n",[65,247,248,250],{"class":67,"line":158},[65,249,242],{"class":71},[65,251,252],{"class":79}," new_value;\n",[65,254,255],{"class":67,"line":164},[65,256,226],{"emptyLinePlaceholder":225},[65,258,259,262],{"class":67,"line":175},[65,260,261],{"class":71},"    do",[65,263,264],{"class":79}," {\n",[65,266,268,271,273,276,279,281,283],{"class":67,"line":267},8,[65,269,270],{"class":79},"        old_value ",[65,272,139],{"class":71},[65,274,275],{"class":79}," counter.",[65,277,278],{"class":75},"load",[65,280,80],{"class":79},[65,282,205],{"class":83},[65,284,285],{"class":79},"::memory_order_relaxed);\n",[65,287,289,292,294,297,300,303],{"class":67,"line":288},9,[65,290,291],{"class":79},"        new_value ",[65,293,139],{"class":71},[65,295,296],{"class":79}," old_value ",[65,298,299],{"class":71},"+",[65,301,302],{"class":151}," 1",[65,304,155],{"class":79},[65,306,308,311,314,316,319,322,325],{"class":67,"line":307},10,[65,309,310],{"class":79},"    } ",[65,312,313],{"class":71},"while",[65,315,116],{"class":79},[65,317,318],{"class":71},"!",[65,320,321],{"class":79},"counter.",[65,323,324],{"class":75},"compare_exchange_weak",[65,326,327],{"class":79},"(old_value, new_value));\n",[65,329,331],{"class":67,"line":330},11,[65,332,178],{"class":79},[10,334,335],{},"这就是乐观锁的思路：先假设没有冲突，直接尝试修改；如果失败，说明别人抢先改了，那就重新读取、重新计算、再次尝试。",[19,337,339],{"id":338},"aba-问题","ABA 问题",[10,341,342],{},"CAS 只关心「当前值是不是等于期望值」，但它不知道这个值中间有没有被改过。",[10,344,345],{},"比如：",[56,347,352],{"className":348,"code":350,"language":351,"meta":61},[349],"language-text","T1: 线程 1 读到值 A\nT2: 线程 2 把 A 改成 B，又把 B 改回 A\nT3: 线程 1 执行 CAS(A, C)，成功\n","text",[36,353,350],{"__ignoreMap":61},[10,355,356,357,359,360,363],{},"从线程 1 的视角看，值仍然是 ",[36,358,44],{},"，CAS 可以成功；但实际上这个位置已经经历过 ",[36,361,362],{},"A -> B -> A"," 的变化。这就是 ABA 问题。",[10,365,366],{},"常见解决方式是加版本号，把比较对象从单个值变成二元组：",[56,368,371],{"className":369,"code":370,"language":351,"meta":61},[349],"(value, version)\n",[36,372,370],{"__ignoreMap":61},[10,374,375,376,378,379,382],{},"每次修改时版本号递增。这样即使值又变回 ",[36,377,44],{},"，版本号也已经不同，CAS 就能发现中间发生过变化。Java 里的 ",[36,380,381],{},"AtomicStampedReference"," 就是这个思路。",[19,384,386],{"id":385},"busy-polling用-cpu-换延迟","Busy Polling：用 CPU 换延迟",[10,388,389],{},"Busy polling，也叫忙轮询，指线程不睡眠、不阻塞，而是在一个循环里不断检查条件是否满足。",[56,391,393],{"className":58,"code":392,"language":60,"meta":61,"style":61},"while (!condition_ready()) {\n    \u002F\u002F busy polling\n}\n\ndo_work();\n",[36,394,395,409,415,419,423],{"__ignoreMap":61},[65,396,397,399,401,403,406],{"class":67,"line":68},[65,398,313],{"class":71},[65,400,116],{"class":79},[65,402,318],{"class":71},[65,404,405],{"class":75},"condition_ready",[65,407,408],{"class":79},"()) {\n",[65,410,411],{"class":67,"line":110},[65,412,414],{"class":413},"sgHix","    \u002F\u002F busy polling\n",[65,416,417],{"class":67,"line":131},[65,418,178],{"class":79},[65,420,421],{"class":67,"line":145},[65,422,226],{"emptyLinePlaceholder":225},[65,424,425,428],{"class":67,"line":158},[65,426,427],{"class":75},"do_work",[65,429,430],{"class":79},"();\n",[10,432,433],{},"它和阻塞等待的差异很直接：",[435,436,437,456],"table",{},[438,439,440],"thead",{},[441,442,443,447,450,453],"tr",{},[444,445,446],"th",{},"等待方式",[444,448,449],{},"行为",[444,451,452],{},"CPU 消耗",[444,454,455],{},"延迟",[457,458,459,474],"tbody",{},[441,460,461,465,468,471],{},[462,463,464],"td",{},"Blocking",[462,466,467],{},"睡眠，让出 CPU",[462,469,470],{},"低",[462,472,473],{},"较高，有调度唤醒成本",[441,475,476,479,482,485],{},[462,477,478],{},"Busy polling",[462,480,481],{},"一直检查条件",[462,483,484],{},"高",[462,486,487],{},"极低，条件满足后立刻响应",[10,489,490,491,17],{},"busy polling 的核心 trade-off 是：",[14,492,493],{},"牺牲一个 CPU 核的利用率，换更低的响应延迟",[10,495,496],{},"所以它常见于这些场景：",[30,498,499,502,505,508],{},[33,500,501],{},"高频交易：绑定专用核心轮询网卡队列，尽量避免调度和中断延迟",[33,503,504],{},"网络驱动：Linux NAPI 会在高流量场景下从中断模式切到轮询模式",[33,506,507],{},"无锁队列消费者：消费者线程不断检查队列是否有新数据",[33,509,510],{},"自旋锁：本质上就是 busy polling 一个锁变量",[10,512,513],{},"实际工程里通常不会无限空转，而是采用混合策略：",[56,515,518],{"className":516,"code":517,"language":351,"meta":61},[349],"先 spin 一小段时间\n  -> 还没等到，就 yield 让出时间片\n  -> 再等不到，就 futex\u002Fcondition_variable 真正睡眠\n",[36,519,517],{"__ignoreMap":61},[10,521,522],{},"这样既能优化短等待场景，又不会在长等待时持续烧 CPU。",[19,524,526],{"id":525},"atomic-ring-buffer低延迟队列的常见形态","Atomic Ring Buffer：低延迟队列的常见形态",[10,528,529],{},"ring buffer 是一个固定大小的循环数组。生产者往里写，消费者从里读，读写位置走到数组尾部后再绕回开头。",[56,531,534],{"className":532,"code":533,"language":351,"meta":61},[349],"slots: [ _ | A | B | C | _ | _ ]\n             ^           ^\n          read_idx    write_idx\n",[36,535,533],{"__ignoreMap":61},[10,537,538],{},"基础结构大概是：",[56,540,542],{"className":58,"code":541,"language":60,"meta":61,"style":61},"template \u003Ctypename T, size_t N>\nstruct RingBuffer {\n    T slots[N];\n    std::atomic\u003Csize_t> write_idx{0};\n    std::atomic\u003Csize_t> read_idx{0};\n};\n",[36,543,544,569,579,584,601,616],{"__ignoreMap":61},[65,545,546,549,552,555,558,560,563,566],{"class":67,"line":68},[65,547,548],{"class":71},"template",[65,550,551],{"class":79}," \u003C",[65,553,554],{"class":71},"typename",[65,556,557],{"class":83}," T",[65,559,91],{"class":79},[65,561,562],{"class":71},"size_t",[65,564,565],{"class":83}," N",[65,567,568],{"class":79},">\n",[65,570,571,574,577],{"class":67,"line":110},[65,572,573],{"class":71},"struct",[65,575,576],{"class":83}," RingBuffer",[65,578,264],{"class":79},[65,580,581],{"class":67,"line":131},[65,582,583],{"class":79},"    T slots[N];\n",[65,585,586,589,591,594,597,599],{"class":67,"line":145},[65,587,588],{"class":83},"    std",[65,590,208],{"class":79},[65,592,593],{"class":71},"\u003Csize_t>",[65,595,596],{"class":79}," write_idx{",[65,598,217],{"class":151},[65,600,220],{"class":79},[65,602,603,605,607,609,612,614],{"class":67,"line":158},[65,604,588],{"class":83},[65,606,208],{"class":79},[65,608,593],{"class":71},[65,610,611],{"class":79}," read_idx{",[65,613,217],{"class":151},[65,615,220],{"class":79},[65,617,618],{"class":67,"line":164},[65,619,220],{"class":79},[10,621,622,623,626,627,630],{},"一般会让 ",[36,624,625],{},"read_idx"," 和 ",[36,628,629],{},"write_idx"," 永远递增，然后通过取模定位数组下标：",[56,632,634],{"className":58,"code":633,"language":60,"meta":61,"style":61},"size_t index = pos % N;\n",[36,635,636],{"__ignoreMap":61},[65,637,638,640,643,645,648,651],{"class":67,"line":68},[65,639,562],{"class":71},[65,641,642],{"class":79}," index ",[65,644,139],{"class":71},[65,646,647],{"class":79}," pos ",[65,649,650],{"class":71},"%",[65,652,653],{"class":79}," N;\n",[10,655,656],{},"这样可以更容易判断空和满：",[56,658,661],{"className":659,"code":660,"language":351,"meta":61},[349],"read_idx == write_idx       -> 空\nwrite_idx - read_idx == N   -> 满\n",[36,662,660],{"__ignoreMap":61},[19,664,666],{"id":665},"spsc-ring-buffer单生产者单消费者","SPSC Ring Buffer：单生产者单消费者",[10,668,669,670,672,673,675],{},"SPSC 是 Single Producer Single Consumer。因为只有一个生产者写 ",[36,671,629],{},"，只有一个消费者写 ",[36,674,625],{},"，所以这个版本甚至不需要 CAS。",[10,677,678],{},"生产者：",[56,680,682],{"className":58,"code":681,"language":60,"meta":61,"style":61},"bool push(const T& value) {\n    size_t write_pos = write_idx.load(std::memory_order_relaxed);\n    size_t read_pos = read_idx.load(std::memory_order_acquire);\n\n    if (write_pos - read_pos == N) {\n        return false;\n    }\n\n    slots[write_pos % N] = value;\n    write_idx.store(write_pos + 1, std::memory_order_release);\n    return true;\n}\n",[36,683,684,706,727,748,752,769,777,781,785,800,822,830],{"__ignoreMap":61},[65,685,686,688,691,693,696,698,701,704],{"class":67,"line":68},[65,687,72],{"class":71},[65,689,690],{"class":75}," push",[65,692,80],{"class":79},[65,694,695],{"class":71},"const",[65,697,557],{"class":83},[65,699,700],{"class":71},"&",[65,702,703],{"class":87}," value",[65,705,107],{"class":79},[65,707,708,711,714,716,719,721,723,725],{"class":67,"line":110},[65,709,710],{"class":71},"    size_t",[65,712,713],{"class":79}," write_pos ",[65,715,139],{"class":71},[65,717,718],{"class":79}," write_idx.",[65,720,278],{"class":75},[65,722,80],{"class":79},[65,724,205],{"class":83},[65,726,285],{"class":79},[65,728,729,731,734,736,739,741,743,745],{"class":67,"line":131},[65,730,710],{"class":71},[65,732,733],{"class":79}," read_pos ",[65,735,139],{"class":71},[65,737,738],{"class":79}," read_idx.",[65,740,278],{"class":75},[65,742,80],{"class":79},[65,744,205],{"class":83},[65,746,747],{"class":79},"::memory_order_acquire);\n",[65,749,750],{"class":67,"line":145},[65,751,226],{"emptyLinePlaceholder":225},[65,753,754,756,759,762,764,766],{"class":67,"line":158},[65,755,113],{"class":71},[65,757,758],{"class":79}," (write_pos ",[65,760,761],{"class":71},"-",[65,763,733],{"class":79},[65,765,125],{"class":71},[65,767,768],{"class":79}," N) {\n",[65,770,771,773,775],{"class":67,"line":164},[65,772,148],{"class":71},[65,774,170],{"class":151},[65,776,155],{"class":79},[65,778,779],{"class":67,"line":175},[65,780,161],{"class":79},[65,782,783],{"class":67,"line":267},[65,784,226],{"emptyLinePlaceholder":225},[65,786,787,790,792,795,797],{"class":67,"line":288},[65,788,789],{"class":79},"    slots[write_pos ",[65,791,650],{"class":71},[65,793,794],{"class":79}," N] ",[65,796,139],{"class":71},[65,798,799],{"class":79}," value;\n",[65,801,802,805,808,811,813,815,817,819],{"class":67,"line":307},[65,803,804],{"class":79},"    write_idx.",[65,806,807],{"class":75},"store",[65,809,810],{"class":79},"(write_pos ",[65,812,299],{"class":71},[65,814,302],{"class":151},[65,816,91],{"class":79},[65,818,205],{"class":83},[65,820,821],{"class":79},"::memory_order_release);\n",[65,823,824,826,828],{"class":67,"line":330},[65,825,167],{"class":71},[65,827,152],{"class":151},[65,829,155],{"class":79},[65,831,833],{"class":67,"line":832},12,[65,834,178],{"class":79},[10,836,837],{},"消费者：",[56,839,841],{"className":58,"code":840,"language":60,"meta":61,"style":61},"bool pop(T& value) {\n    size_t read_pos = read_idx.load(std::memory_order_relaxed);\n    size_t write_pos = write_idx.load(std::memory_order_acquire);\n\n    if (read_pos == write_pos) {\n        return false;\n    }\n\n    value = slots[read_pos % N];\n    read_idx.store(read_pos + 1, std::memory_order_release);\n    return true;\n}\n",[36,842,843,861,879,897,901,913,921,925,929,944,964,972],{"__ignoreMap":61},[65,844,845,847,850,852,855,857,859],{"class":67,"line":68},[65,846,72],{"class":71},[65,848,849],{"class":75}," pop",[65,851,80],{"class":79},[65,853,854],{"class":83},"T",[65,856,700],{"class":71},[65,858,703],{"class":87},[65,860,107],{"class":79},[65,862,863,865,867,869,871,873,875,877],{"class":67,"line":110},[65,864,710],{"class":71},[65,866,733],{"class":79},[65,868,139],{"class":71},[65,870,738],{"class":79},[65,872,278],{"class":75},[65,874,80],{"class":79},[65,876,205],{"class":83},[65,878,285],{"class":79},[65,880,881,883,885,887,889,891,893,895],{"class":67,"line":131},[65,882,710],{"class":71},[65,884,713],{"class":79},[65,886,139],{"class":71},[65,888,718],{"class":79},[65,890,278],{"class":75},[65,892,80],{"class":79},[65,894,205],{"class":83},[65,896,747],{"class":79},[65,898,899],{"class":67,"line":145},[65,900,226],{"emptyLinePlaceholder":225},[65,902,903,905,908,910],{"class":67,"line":158},[65,904,113],{"class":71},[65,906,907],{"class":79}," (read_pos ",[65,909,125],{"class":71},[65,911,912],{"class":79}," write_pos) {\n",[65,914,915,917,919],{"class":67,"line":164},[65,916,148],{"class":71},[65,918,170],{"class":151},[65,920,155],{"class":79},[65,922,923],{"class":67,"line":175},[65,924,161],{"class":79},[65,926,927],{"class":67,"line":267},[65,928,226],{"emptyLinePlaceholder":225},[65,930,931,934,936,939,941],{"class":67,"line":288},[65,932,933],{"class":79},"    value ",[65,935,139],{"class":71},[65,937,938],{"class":79}," slots[read_pos ",[65,940,650],{"class":71},[65,942,943],{"class":79}," N];\n",[65,945,946,949,951,954,956,958,960,962],{"class":67,"line":307},[65,947,948],{"class":79},"    read_idx.",[65,950,807],{"class":75},[65,952,953],{"class":79},"(read_pos ",[65,955,299],{"class":71},[65,957,302],{"class":151},[65,959,91],{"class":79},[65,961,205],{"class":83},[65,963,821],{"class":79},[65,965,966,968,970],{"class":67,"line":330},[65,967,167],{"class":71},[65,969,152],{"class":151},[65,971,155],{"class":79},[65,973,974],{"class":67,"line":832},[65,975,178],{"class":79},[10,977,978],{},"这里的重点是：",[30,980,981,990],{},[33,982,983,986,987,989],{},[36,984,985],{},"slots[...] = value"," 必须发生在 ",[36,988,629],{}," 对消费者可见之前",[33,991,992,993,995],{},"消费者看到新的 ",[36,994,629],{}," 后，必须能看到对应 slot 里的数据",[10,997,998,999,626,1002,1005],{},"这就是 ",[36,1000,1001],{},"release store",[36,1003,1004],{},"acquire load"," 要解决的问题。",[19,1007,1009],{"id":1008},"mpmc-ring-buffer多生产者多消费者","MPMC Ring Buffer：多生产者多消费者",[10,1011,1012],{},"MPMC 是 Multi Producer Multi Consumer。多个生产者会同时抢写入位置，多个消费者会同时抢读取位置，这时就需要 CAS。",[10,1014,1015,1016,1019],{},"工业级 MPMC ring buffer 常见设计是给每个 slot 加一个 ",[36,1017,1018],{},"sequence"," 字段：",[56,1021,1023],{"className":58,"code":1022,"language":60,"meta":61,"style":61},"template \u003Ctypename T>\nstruct Slot {\n    T data;\n    std::atomic\u003Csize_t> sequence;\n};\n",[36,1024,1025,1037,1046,1051,1062],{"__ignoreMap":61},[65,1026,1027,1029,1031,1033,1035],{"class":67,"line":68},[65,1028,548],{"class":71},[65,1030,551],{"class":79},[65,1032,554],{"class":71},[65,1034,557],{"class":83},[65,1036,568],{"class":79},[65,1038,1039,1041,1044],{"class":67,"line":110},[65,1040,573],{"class":71},[65,1042,1043],{"class":83}," Slot",[65,1045,264],{"class":79},[65,1047,1048],{"class":67,"line":131},[65,1049,1050],{"class":79},"    T data;\n",[65,1052,1053,1055,1057,1059],{"class":67,"line":145},[65,1054,588],{"class":83},[65,1056,208],{"class":79},[65,1058,593],{"class":71},[65,1060,1061],{"class":79}," sequence;\n",[65,1063,1064],{"class":67,"line":158},[65,1065,220],{"class":79},[10,1067,1068],{},"生产者大致流程是：",[56,1070,1073],{"className":1071,"code":1072,"language":351,"meta":61},[349],"1. 读取 write_idx\n2. 找到对应 slot\n3. 检查 slot.sequence，判断这个槽位是否可写\n4. 用 CAS 抢占 write_idx\n5. 写入 data\n6. release-store sequence，通知消费者可读\n",[36,1074,1072],{"__ignoreMap":61},[10,1076,1077],{},"消费者则反过来：",[56,1079,1082],{"className":1080,"code":1081,"language":351,"meta":61},[349],"1. 读取 read_idx\n2. 找到对应 slot\n3. 检查 slot.sequence，判断这个槽位是否可读\n4. 用 CAS 抢占 read_idx\n5. 读取 data\n6. release-store sequence，通知生产者可复用\n",[36,1083,1081],{"__ignoreMap":61},[10,1085,1086],{},"这种结构比 mutex 队列复杂，但在低延迟、高吞吐场景下非常常见。LMAX Disruptor、很多交易系统和消息队列内部都能看到类似思路。",[19,1088,1090],{"id":1089},"release-store-与-acquire-load","Release Store 与 Acquire Load",[10,1092,1093],{},"先看一个经典问题：",[56,1095,1097],{"className":58,"code":1096,"language":60,"meta":61,"style":61},"\u002F\u002F 线程 1\ndata = 42;\nflag = true;\n\n\u002F\u002F 线程 2\nwhile (!flag) {}\nstd::cout \u003C\u003C data \u003C\u003C std::endl;\n",[36,1098,1099,1104,1116,1127,1131,1136,1147],{"__ignoreMap":61},[65,1100,1101],{"class":67,"line":68},[65,1102,1103],{"class":413},"\u002F\u002F 线程 1\n",[65,1105,1106,1109,1111,1114],{"class":67,"line":110},[65,1107,1108],{"class":79},"data ",[65,1110,139],{"class":71},[65,1112,1113],{"class":151}," 42",[65,1115,155],{"class":79},[65,1117,1118,1121,1123,1125],{"class":67,"line":131},[65,1119,1120],{"class":79},"flag ",[65,1122,139],{"class":71},[65,1124,152],{"class":151},[65,1126,155],{"class":79},[65,1128,1129],{"class":67,"line":145},[65,1130,226],{"emptyLinePlaceholder":225},[65,1132,1133],{"class":67,"line":158},[65,1134,1135],{"class":413},"\u002F\u002F 线程 2\n",[65,1137,1138,1140,1142,1144],{"class":67,"line":164},[65,1139,313],{"class":71},[65,1141,116],{"class":79},[65,1143,318],{"class":71},[65,1145,1146],{"class":79},"flag) {}\n",[65,1148,1149,1151,1154,1157,1160,1162,1165],{"class":67,"line":175},[65,1150,205],{"class":83},[65,1152,1153],{"class":79},"::cout ",[65,1155,1156],{"class":71},"\u003C\u003C",[65,1158,1159],{"class":79}," data ",[65,1161,1156],{"class":71},[65,1163,1164],{"class":83}," std",[65,1166,1167],{"class":79},"::endl;\n",[10,1169,1170,1171,1174,1175,1178],{},"直觉上，线程 2 看到 ",[36,1172,1173],{},"flag == true"," 后，应该一定能看到 ",[36,1176,1177],{},"data == 42","。但在多核 CPU 和编译器优化下，如果没有同步语义，这个保证并不成立。",[10,1180,1181],{},"正确写法是：",[56,1183,1185],{"className":58,"code":1184,"language":60,"meta":61,"style":61},"\u002F\u002F 线程 1\ndata = 42;\nflag.store(true, std::memory_order_release);\n\n\u002F\u002F 线程 2\nwhile (!flag.load(std::memory_order_acquire)) {}\nstd::cout \u003C\u003C data \u003C\u003C std::endl;\n",[36,1186,1187,1191,1201,1219,1223,1227,1246],{"__ignoreMap":61},[65,1188,1189],{"class":67,"line":68},[65,1190,1103],{"class":413},[65,1192,1193,1195,1197,1199],{"class":67,"line":110},[65,1194,1108],{"class":79},[65,1196,139],{"class":71},[65,1198,1113],{"class":151},[65,1200,155],{"class":79},[65,1202,1203,1206,1208,1210,1213,1215,1217],{"class":67,"line":131},[65,1204,1205],{"class":79},"flag.",[65,1207,807],{"class":75},[65,1209,80],{"class":79},[65,1211,1212],{"class":151},"true",[65,1214,91],{"class":79},[65,1216,205],{"class":83},[65,1218,821],{"class":79},[65,1220,1221],{"class":67,"line":145},[65,1222,226],{"emptyLinePlaceholder":225},[65,1224,1225],{"class":67,"line":158},[65,1226,1135],{"class":413},[65,1228,1229,1231,1233,1235,1237,1239,1241,1243],{"class":67,"line":164},[65,1230,313],{"class":71},[65,1232,116],{"class":79},[65,1234,318],{"class":71},[65,1236,1205],{"class":79},[65,1238,278],{"class":75},[65,1240,80],{"class":79},[65,1242,205],{"class":83},[65,1244,1245],{"class":79},"::memory_order_acquire)) {}\n",[65,1247,1248,1250,1252,1254,1256,1258,1260],{"class":67,"line":175},[65,1249,205],{"class":83},[65,1251,1153],{"class":79},[65,1253,1156],{"class":71},[65,1255,1159],{"class":79},[65,1257,1156],{"class":71},[65,1259,1164],{"class":83},[65,1261,1167],{"class":79},[10,1263,1264],{},"可以这样理解：",[30,1266,1267,1272],{},[33,1268,1269,1271],{},[36,1270,1001],{},"：这条 store 之前的读写，不能被重排到它之后；并且要对看到它的线程可见",[33,1273,1274,1276],{},[36,1275,1004],{},"：这条 load 之后的读写，不能被重排到它之前；如果它看到了 release-store 写入的值，也能看到 release 之前的写入",[10,1278,1279,1280,1282,1283,1285],{},"当一个 ",[36,1281,1004],{}," 读到了另一个线程 ",[36,1284,1001],{}," 写入的值，它们之间就建立了 happens-before 关系。",[10,1287,1288],{},"在 ring buffer 里，这个语义非常关键：",[56,1290,1292],{"className":58,"code":1291,"language":60,"meta":61,"style":61},"\u002F\u002F producer\nslot->data = value;\nslot->sequence.store(pos + 1, std::memory_order_release);\n\n\u002F\u002F consumer\nsize_t sequence = slot->sequence.load(std::memory_order_acquire);\n\u002F\u002F 如果 sequence 表示可读，那么这里一定能看到 producer 写入的 data\n",[36,1293,1294,1299,1308,1328,1332,1337,1357],{"__ignoreMap":61},[65,1295,1296],{"class":67,"line":68},[65,1297,1298],{"class":413},"\u002F\u002F producer\n",[65,1300,1301,1304,1306],{"class":67,"line":110},[65,1302,1303],{"class":79},"slot->data ",[65,1305,139],{"class":71},[65,1307,799],{"class":79},[65,1309,1310,1313,1315,1318,1320,1322,1324,1326],{"class":67,"line":131},[65,1311,1312],{"class":79},"slot->sequence.",[65,1314,807],{"class":75},[65,1316,1317],{"class":79},"(pos ",[65,1319,299],{"class":71},[65,1321,302],{"class":151},[65,1323,91],{"class":79},[65,1325,205],{"class":83},[65,1327,821],{"class":79},[65,1329,1330],{"class":67,"line":145},[65,1331,226],{"emptyLinePlaceholder":225},[65,1333,1334],{"class":67,"line":158},[65,1335,1336],{"class":413},"\u002F\u002F consumer\n",[65,1338,1339,1341,1344,1346,1349,1351,1353,1355],{"class":67,"line":164},[65,1340,562],{"class":71},[65,1342,1343],{"class":79}," sequence ",[65,1345,139],{"class":71},[65,1347,1348],{"class":79}," slot->sequence.",[65,1350,278],{"class":75},[65,1352,80],{"class":79},[65,1354,205],{"class":83},[65,1356,747],{"class":79},[65,1358,1359],{"class":67,"line":175},[65,1360,1361],{"class":413},"\u002F\u002F 如果 sequence 表示可读，那么这里一定能看到 producer 写入的 data\n",[10,1363,1364,1365,1368,1369,1371,1372,1375],{},"如果把这里全部换成 ",[36,1366,1367],{},"memory_order_relaxed","，消费者可能先看到 ",[36,1370,1018],{}," 更新，却还看不到对应的 ",[36,1373,1374],{},"data"," 写入。",[19,1377,1379],{"id":1378},"store-buffer为什么写入不会立刻被别的核心看到","Store Buffer：为什么写入不会立刻被别的核心看到",[10,1381,1382],{},"现代 CPU 不会每次写内存都停下来等缓存一致性协议完成。为了提高性能，核心通常会先把写入放进 store buffer，然后继续执行后续指令。",[10,1384,1385],{},"可以把它想成这样：",[56,1387,1390],{"className":1388,"code":1389,"language":351,"meta":61},[349],"CPU Core\n  -> 执行 store x = 1\n  -> 写入先进入 store buffer\n  -> CPU 继续往后执行\n  -> store buffer 异步把写入刷到 cache，并通过一致性协议让其他核心可见\n",[36,1391,1389],{"__ignoreMap":61},[10,1393,1394,1395,17],{},"这带来一个重要现象：",[14,1396,1397],{},"当前核心能通过 store-to-load forwarding 看到自己刚写的值，但其他核心可能暂时看不到",[10,1399,1400],{},"例如：",[56,1402,1404],{"className":58,"code":1403,"language":60,"meta":61,"style":61},"\u002F\u002F 初始 x = 0, y = 0\n\n\u002F\u002F Core 0                 \u002F\u002F Core 1\nx = 1;                    y = 1;\nr1 = y;                   r2 = x;\n",[36,1405,1406,1411,1415,1423,1441],{"__ignoreMap":61},[65,1407,1408],{"class":67,"line":68},[65,1409,1410],{"class":413},"\u002F\u002F 初始 x = 0, y = 0\n",[65,1412,1413],{"class":67,"line":110},[65,1414,226],{"emptyLinePlaceholder":225},[65,1416,1417,1420],{"class":67,"line":131},[65,1418,1419],{"class":413},"\u002F\u002F Core 0",[65,1421,1422],{"class":413},"                 \u002F\u002F Core 1\n",[65,1424,1425,1428,1430,1432,1435,1437,1439],{"class":67,"line":145},[65,1426,1427],{"class":79},"x ",[65,1429,139],{"class":71},[65,1431,302],{"class":151},[65,1433,1434],{"class":79},";                    y ",[65,1436,139],{"class":71},[65,1438,302],{"class":151},[65,1440,155],{"class":79},[65,1442,1443,1446,1448,1451,1453],{"class":67,"line":158},[65,1444,1445],{"class":79},"r1 ",[65,1447,139],{"class":71},[65,1449,1450],{"class":79}," y;                   r2 ",[65,1452,139],{"class":71},[65,1454,1455],{"class":79}," x;\n",[10,1457,1458],{},"在弱内存模型下，可能出现：",[56,1460,1463],{"className":1461,"code":1462,"language":351,"meta":61},[349],"r1 == 0 && r2 == 0\n",[36,1464,1462],{"__ignoreMap":61},[10,1466,1467],{},"因为两个核心的写入都还停留在各自的 store buffer 里，对方暂时看不到。",[10,1469,1470],{},"memory barrier、release\u002Facquire 等机制，本质上就是在约束这些乱序和可见性问题：什么时候允许写入继续留在 buffer 里，什么时候必须让之前的写入对其他核心可见。",[19,1472,1474],{"id":1473},"false-sharing无锁结构里的隐形性能杀手","False Sharing：无锁结构里的隐形性能杀手",[10,1476,1477],{},"CPU cache 不是按单个变量加载的，而是按 cache line 加载。常见 cache line 大小是 64 字节。",[10,1479,1480],{},"如果两个频繁更新的原子变量刚好落在同一个 cache line 上，即使它们逻辑上毫无关系，也会互相拖慢。",[10,1482,345],{},[56,1484,1486],{"className":58,"code":1485,"language":60,"meta":61,"style":61},"struct BadLayout {\n    std::atomic\u003Csize_t> write_idx;\n    std::atomic\u003Csize_t> read_idx;\n};\n",[36,1487,1488,1497,1508,1519],{"__ignoreMap":61},[65,1489,1490,1492,1495],{"class":67,"line":68},[65,1491,573],{"class":71},[65,1493,1494],{"class":83}," BadLayout",[65,1496,264],{"class":79},[65,1498,1499,1501,1503,1505],{"class":67,"line":110},[65,1500,588],{"class":83},[65,1502,208],{"class":79},[65,1504,593],{"class":71},[65,1506,1507],{"class":79}," write_idx;\n",[65,1509,1510,1512,1514,1516],{"class":67,"line":131},[65,1511,588],{"class":83},[65,1513,208],{"class":79},[65,1515,593],{"class":71},[65,1517,1518],{"class":79}," read_idx;\n",[65,1520,1521],{"class":67,"line":145},[65,1522,220],{"class":79},[10,1524,1525,1526,1528,1529,1531],{},"生产者不断写 ",[36,1527,629],{},"，消费者不断写 ",[36,1530,625],{},"。如果两个变量在同一条 cache line 上，每次一个核心写入，都会导致另一个核心对应 cache line 失效。",[10,1533,1534],{},"更好的做法是让它们分开：",[56,1536,1538],{"className":58,"code":1537,"language":60,"meta":61,"style":61},"struct alignas(64) PaddedAtomicSize {\n    std::atomic\u003Csize_t> value;\n};\n\nstruct BetterLayout {\n    PaddedAtomicSize write_idx;\n    PaddedAtomicSize read_idx;\n};\n",[36,1539,1540,1552,1562,1566,1570,1579,1584,1589],{"__ignoreMap":61},[65,1541,1542,1544,1547,1550],{"class":67,"line":68},[65,1543,573],{"class":71},[65,1545,1546],{"class":151}," alignas(64)",[65,1548,1549],{"class":83}," PaddedAtomicSize",[65,1551,264],{"class":79},[65,1553,1554,1556,1558,1560],{"class":67,"line":110},[65,1555,588],{"class":83},[65,1557,208],{"class":79},[65,1559,593],{"class":71},[65,1561,799],{"class":79},[65,1563,1564],{"class":67,"line":131},[65,1565,220],{"class":79},[65,1567,1568],{"class":67,"line":145},[65,1569,226],{"emptyLinePlaceholder":225},[65,1571,1572,1574,1577],{"class":67,"line":158},[65,1573,573],{"class":71},[65,1575,1576],{"class":83}," BetterLayout",[65,1578,264],{"class":79},[65,1580,1581],{"class":67,"line":164},[65,1582,1583],{"class":79},"    PaddedAtomicSize write_idx;\n",[65,1585,1586],{"class":67,"line":175},[65,1587,1588],{"class":79},"    PaddedAtomicSize read_idx;\n",[65,1590,1591],{"class":67,"line":267},[65,1592,220],{"class":79},[10,1594,1595],{},"在低延迟队列、线程池计数器、统计指标里，false sharing 经常是性能抖动的来源。",[19,1597,1599],{"id":1598},"cpu-cache-warmup为什么第一次跑总是慢","CPU Cache Warmup：为什么第一次跑总是慢",[10,1601,1602],{},"CPU cache warmup 指让数据和指令逐渐进入 cache 的过程。",[10,1604,1605],{},"程序刚启动时，相关数据往往不在 cache 里，第一次访问会经历多级 cache miss：",[56,1607,1610],{"className":1608,"code":1609,"language":351,"meta":61},[349],"L1 miss -> L2 miss -> L3 miss -> DRAM\n",[36,1611,1609],{"__ignoreMap":61},[10,1613,1614],{},"访问延迟大概可以这样理解：",[56,1616,1619],{"className":1617,"code":1618,"language":351,"meta":61},[349],"L1 cache: 几个 cycle\nL2 cache: 十几个 cycle\nL3 cache: 几十个 cycle\nDRAM:     上百个 cycle\n",[36,1620,1618],{"__ignoreMap":61},[10,1622,1623],{},"所以同一段代码，第一次跑和预热后再跑，耗时可能差很多。",[10,1625,1626],{},"这也是 benchmark 需要 warmup 的原因：",[56,1628,1630],{"className":58,"code":1629,"language":60,"meta":61,"style":61},"for (int i = 0; i \u003C warmup_iters; ++i) {\n    run_once(); \u002F\u002F 丢弃结果，只为预热 cache、分支预测、JIT 等\n}\n\nfor (int i = 0; i \u003C benchmark_iters; ++i) {\n    measure(run_once);\n}\n",[36,1631,1632,1665,1676,1680,1684,1709,1717],{"__ignoreMap":61},[65,1633,1634,1637,1639,1642,1645,1647,1650,1653,1656,1659,1662],{"class":67,"line":68},[65,1635,1636],{"class":71},"for",[65,1638,116],{"class":79},[65,1640,1641],{"class":71},"int",[65,1643,1644],{"class":79}," i ",[65,1646,139],{"class":71},[65,1648,1649],{"class":151}," 0",[65,1651,1652],{"class":79},"; i ",[65,1654,1655],{"class":71},"\u003C",[65,1657,1658],{"class":79}," warmup_iters; ",[65,1660,1661],{"class":71},"++",[65,1663,1664],{"class":79},"i) {\n",[65,1666,1667,1670,1673],{"class":67,"line":110},[65,1668,1669],{"class":75},"    run_once",[65,1671,1672],{"class":79},"();",[65,1674,1675],{"class":413}," \u002F\u002F 丢弃结果，只为预热 cache、分支预测、JIT 等\n",[65,1677,1678],{"class":67,"line":131},[65,1679,178],{"class":79},[65,1681,1682],{"class":67,"line":145},[65,1683,226],{"emptyLinePlaceholder":225},[65,1685,1686,1688,1690,1692,1694,1696,1698,1700,1702,1705,1707],{"class":67,"line":158},[65,1687,1636],{"class":71},[65,1689,116],{"class":79},[65,1691,1641],{"class":71},[65,1693,1644],{"class":79},[65,1695,139],{"class":71},[65,1697,1649],{"class":151},[65,1699,1652],{"class":79},[65,1701,1655],{"class":71},[65,1703,1704],{"class":79}," benchmark_iters; ",[65,1706,1661],{"class":71},[65,1708,1664],{"class":79},[65,1710,1711,1714],{"class":67,"line":164},[65,1712,1713],{"class":75},"    measure",[65,1715,1716],{"class":79},"(run_once);\n",[65,1718,1719],{"class":67,"line":175},[65,1720,178],{"class":79},[10,1722,1723],{},"在 ring buffer 场景下，如果 slots 数组大小能放进 cache，跑过几轮后访问会稳定很多；如果 ring buffer 远大于 LLC，warmup 的收益就会明显下降，因为数据不断被换出 cache。",[19,1725,1726],{"id":1726},"这些概念怎么串起来",[10,1728,1729],{},"可以用一个低延迟消息队列来串联：",[56,1731,1734],{"className":1732,"code":1733,"language":351,"meta":61},[349],"1. 生产者用 CAS 抢占 ring buffer 的写入位置\n2. 写入 slot.data\n3. 用 release store 发布 sequence\n4. 消费者 busy polling sequence\n5. 用 acquire load 看到 sequence 更新\n6. 安全读取 slot.data\n7. 通过 padding 避免 false sharing\n8. 通过 warmup 减少 cold cache 带来的尾延迟\n",[36,1735,1733],{"__ignoreMap":61},[10,1737,1738],{},"它们不是孤立知识点，而是同一套低延迟并发系统里的不同层次：",[30,1740,1741,1744,1747,1750,1753,1756],{},[33,1742,1743],{},"CAS 解决「多个线程怎么无锁抢同一个位置」",[33,1745,1746],{},"release\u002Facquire 解决「写入顺序和可见性怎么保证」",[33,1748,1749],{},"store buffer 解释「为什么可见性不是天然成立的」",[33,1751,1752],{},"busy polling 解决「如何避免阻塞唤醒延迟」",[33,1754,1755],{},"ring buffer 提供「固定内存、cache-friendly 的队列结构」",[33,1757,1758],{},"false sharing 和 cache warmup 处理「真实性能为什么和代码看起来不一样」",[19,1760,1761],{"id":1761},"总结",[10,1763,1764],{},"无锁并发不是简单地把 mutex 换成 atomic。它真正难的地方在于：",[1766,1767,1768,1771,1774],"ol",{},[33,1769,1770],{},"正确性依赖原子操作和内存序。",[33,1772,1773],{},"性能依赖 cache line、store buffer、预取和调度行为。",[33,1775,1776],{},"低延迟通常不是免费得到的，而是用 CPU、复杂度和可维护性换来的。",[10,1778,1779],{},"所以工程上要先问清楚：",[30,1781,1782,1785,1788,1791,1794],{},[33,1783,1784],{},"是否真的需要无锁？",[33,1786,1787],{},"竞争是低还是高？",[33,1789,1790],{},"等待时间是短还是长？",[33,1792,1793],{},"是追求吞吐，还是追求尾延迟？",[33,1795,1796],{},"数据结构能不能放进 cache？",[10,1798,1799],{},"如果只是普通业务并发，mutex、condition_variable 和线程池往往已经足够；如果是交易系统、网络包处理、实时音视频这类低延迟场景，CAS、busy polling、atomic ring buffer 和 cache-aware 优化才真正值得投入。",[1801,1802,1803],"style",{},"html pre.shiki code .s6PUj, html code.shiki .s6PUj{--shiki-default:#F47067;--shiki-light:#D73A49}html pre.shiki code .saVmf, html code.shiki .saVmf{--shiki-default:#DCBDFB;--shiki-light:#6F42C1}html pre.shiki code .ssh_m, html code.shiki .ssh_m{--shiki-default:#ADBAC7;--shiki-light:#24292E}html pre.shiki code .sqRhv, html code.shiki .sqRhv{--shiki-default:#F69D50;--shiki-light:#6F42C1}html pre.shiki code .sNjOc, html code.shiki .sNjOc{--shiki-default:#F69D50;--shiki-light:#E36209}html pre.shiki code .swcJU, html code.shiki .swcJU{--shiki-default:#6CB6FF;--shiki-light:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sgHix, html code.shiki .sgHix{--shiki-default:#768390;--shiki-light:#6A737D}",{"title":61,"searchDepth":110,"depth":131,"links":1805},[1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817],{"id":21,"depth":110,"text":22},{"id":338,"depth":110,"text":339},{"id":385,"depth":110,"text":386},{"id":525,"depth":110,"text":526},{"id":665,"depth":110,"text":666},{"id":1008,"depth":110,"text":1009},{"id":1089,"depth":110,"text":1090},{"id":1378,"depth":110,"text":1379},{"id":1473,"depth":110,"text":1474},{"id":1598,"depth":110,"text":1599},{"id":1726,"depth":110,"text":1726},{"id":1761,"depth":110,"text":1761},[1819],"技术","2026-04-25","这篇文章整理几个无锁并发里经常一起出现的概念：CAS、busy polling、atomic ring buffer、release\u002Facquire、store buffer 和 CPU cache warmup。它们看起来分散，其实是一条完整链路：硬件提供原子操作，程序用原子变量做同步，线程用轮询降低延迟，最后还要理解 cache 和内存模型带来的性能与可见性问题。",false,"md",{},"\u002Fposts\u002Flock-free-concurrency-notes",{"title":5,"description":1821},"posts\u002Flock-free-concurrency-notes",[1829,1830,1831,1832,1833],"C++","并发","无锁编程","性能优化","量化开发","yAU5cW-hhPgHwaL7Z62gNDYw9Ln5nb00OkJRogatVkY",[1836,1849,1861,1867,1880,1889,1898,1908,1919,1928,1938,1950,1963,1975,1984,1994,2007,2018,2028,2036,2039,2045,2051,2057,2065,2074,2082,2088,2096,2104],{"slug":1837,"path":1838,"title":1839,"date":1840,"tags":1841,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":288},"multimodal-rag-from-scratch","\u002Fposts\u002Fmultimodal-rag-from-scratch","从零实现多模态 RAG：BM25、Dense 检索、RRF 融合、MMR 重排全部手写","2026-06-30 18:00:00",[1842,1843,1844,1845,1846,1847,1848],"RAG","多模态","AI Infra","BM25","向量检索","混合检索","实习求职",{"slug":1850,"path":1851,"title":1852,"date":1853,"tags":1854,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":330},"mini-llm-engine-deep-dive","\u002Fposts\u002Fmini-llm-engine-deep-dive","讲透 mini-llm-engine：从显存碎片到六大推理优化","2026-06-30 14:00:00",[1855,1844,1856,1857,1858,1859,1860,1848],"LLM","vLLM","PagedAttention","KV Cache","推理优化","投机解码",{"slug":1862,"path":1863,"title":1864,"date":1865,"tags":1866,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":307},"mini-llm-engine-from-scratch","\u002Fposts\u002Fmini-llm-engine-from-scratch","从零实现 LLM 推理引擎：深挖 vLLM 的六大核心优化","2026-06-30 10:00:00",[1855,1844,1856,1857,1859,1860,1848],{"slug":1868,"path":1869,"title":1870,"date":1871,"tags":1872,"description":1878,"draft":1822,"hidden":1822,"published":225,"readingTime":1879},"bytedance-recommendation-architecture-intern-interview","\u002Fposts\u002Fbytedance-recommendation-architecture-intern-interview","字节推荐架构实习生 Data 面试准备：推荐系统、实时特征与高并发八股","2026-06-09",[1873,1874,1875,1876,1877],"面试","推荐系统","后端架构","实时计算","字节跳动","面向字节跳动推荐架构团队实习岗位的八股准备清单，覆盖推荐系统链路、实时数据、特征服务、高并发后端、分布式系统与项目包装。",18,{"slug":1881,"path":1882,"title":1883,"date":1871,"tags":1884,"description":1888,"draft":1822,"hidden":1822,"published":225,"readingTime":832},"high-concurrency-rate-limiting-algorithms","\u002Fposts\u002Fhigh-concurrency-rate-limiting-algorithms","高并发限流算法：固定窗口、滑动窗口、漏桶与令牌桶",[1885,1886,1875,1887,1873],"高并发","限流","系统设计","面试和工程都常见的限流算法总结，讲清楚固定窗口、滑动窗口、漏桶、令牌桶、并发数限制以及分布式限流如何落地。",{"slug":1890,"path":1891,"title":1892,"date":1871,"tags":1893,"description":1897,"draft":1822,"hidden":1822,"published":225,"readingTime":267},"kafka-producer-broker-consumer","\u002Fposts\u002Fkafka-producer-broker-consumer","Kafka 入门：生产者、Broker、消费者和“消费”到底是什么意思",[1894,1895,1896,1873,1874],"Kafka","消息队列","后端","用推荐系统里的用户行为日志为例，讲清楚 Kafka 的作用、Producer、Broker、Consumer、Topic、Partition、Offset 和消费语义。",{"slug":1899,"path":1900,"title":1901,"date":1871,"tags":1902,"description":1907,"draft":1822,"hidden":1822,"published":225,"readingTime":164},"leetcode-lru-merge-k-reverse-list","\u002Fposts\u002Fleetcode-lru-merge-k-reverse-list","链表与缓存高频题：LRU Cache、合并 K 个有序链表、反转链表",[1903,1904,1905,1906,1873],"算法","链表","LRU","LeetCode","面试高频算法题速记，整理 LRU Cache、合并 K 个有序链表、反转链表的核心思路、复杂度和 C++ 代码。",{"slug":1909,"path":1910,"title":1911,"date":1912,"tags":1913,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":1918},"meddococr-interpreter-source-analysis","\u002Fposts\u002Fmeddococr-interpreter-source-analysis","MedDocOCR-Interpreter 源码导读：医疗文档 OCR、结构化抽取与报告解读原型","2026-05-22 10:30:00",[1914,1843,1915,1842,1916,1917],"OCR","医疗 AI","Python","源码分析",16,{"slug":1920,"path":1921,"title":1922,"date":1923,"tags":1924,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":307},"cet6-writing-model-essays","\u002Fposts\u002Fcet6-writing-model-essays","六级写作范文背诵包：10 个高频话题","2026-05-20 09:00:00",[1925,1926,1927],"English","CET6","Writing",{"slug":1929,"path":1930,"title":1931,"date":1932,"tags":1933,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":1937},"claude-code-context-management","\u002Fposts\u002Fclaude-code-context-management","Claude Code 上下文管理机制：从 Microcompact 到 Auto Compact","2026-05-19 10:00:00",[1934,1935,1855,1844,1936],"Claude Code","Agent","上下文工程",27,{"slug":1939,"path":1940,"title":1941,"date":1942,"tags":1943,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":1949},"nanotron-llm-pretraining-framework-analysis","\u002Fposts\u002Fnanotron-llm-pretraining-framework-analysis","Nanotron 项目详解：Hugging Face 的大模型预训练框架怎么做分布式训练","2026-05-10 12:10:00",[1855,1944,1945,1946,1947,1844,1948],"大模型训练","分布式训练","Nanotron","Hugging Face","PyTorch",19,{"slug":1951,"path":1952,"title":1953,"date":1954,"tags":1955,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":1962},"gofoundry-go-backend-foundation-framework","\u002Fposts\u002Fgofoundry-go-backend-foundation-framework","GoFoundry 项目详解：基于 Go 的后端基础框架套件设计","2026-05-10 11:20:00",[1956,1957,1958,1959,1960,1895,1961],"Go","后端框架","ORM","分布式缓存","分布式锁","项目架构",25,{"slug":1964,"path":1965,"title":1966,"date":1967,"tags":1968,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":1937},"cloudvault-go-cloud-storage-system","\u002Fposts\u002Fcloudvault-go-cloud-storage-system","CloudVault 项目详解：基于 Go 的云端存储与网盘系统架构设计","2026-05-10 10:30:00",[1956,1969,1970,1971,1961,1972,1973,1974],"云存储","网盘系统","分布式系统","Redis","RabbitMQ","Elasticsearch",{"slug":1976,"path":1977,"title":1978,"date":1979,"tags":1980,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":1983},"openclaw-source-code-analysis","\u002Fposts\u002Fopenclaw-source-code-analysis","OpenClaw 源码导读：个人 AI 助手的网关、通道、插件与运行时架构","2026-05-08 16:30:00",[1981,1935,1844,1982,1917],"OpenClaw","TypeScript",20,{"slug":1985,"path":1986,"title":1987,"date":1988,"tags":1989,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":307},"flow-matching-generative-models","\u002Fposts\u002Fflow-matching-generative-models","Flow Matching：从噪声到数据的连续流生成模型","2026-05-07 00:00:00",[1990,1991,1992,1993],"生成模型","Diffusion","Flow Matching","深度学习",{"slug":1995,"path":1996,"title":1997,"date":1998,"tags":1999,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":2006},"database-ai-week4","\u002Fposts\u002Fdatabase-ai-week4","Week 4：数据库速成——从 Storage、Index、Query Optimization 到 Vector DB 与 RAG","2026-05-05 12:00:00",[2000,2001,2002,1842,2003,2004,2005],"数据库","CMU 15-445","Vector DB","LLM Memory","Query Optimization","Caching",15,{"slug":2008,"path":2009,"title":2010,"date":2011,"tags":2012,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":1918},"distributed-systems-week3","\u002Fposts\u002Fdistributed-systems-week3","Week 3：分布式系统速成——MapReduce、Raft、容错与 Distributed KV Store","2026-05-05 11:00:00",[1971,2013,2014,2015,2016,2017,1935],"MIT 6.824","MapReduce","Raft","KV Store","Ray",{"slug":2019,"path":2020,"title":2021,"date":2022,"tags":2023,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":2006},"gpu-inference-acceleration-week2","\u002Fposts\u002Fgpu-inference-acceleration-week2","Week 2：GPU 与推理加速——从 Kernel、算子融合到 LLM Serving","2026-05-05 10:00:00",[1993,2024,2025,1855,2026,1856,2027],"GPU","推理加速","CMU 10-414","TensorRT",{"slug":2029,"path":2030,"title":2031,"date":2032,"tags":2033,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":1879},"dl-framework-autograd-mini","\u002Fposts\u002Fdl-framework-autograd-mini","Week 1：DL 框架与 Autograd——从计算图、反向传播到 Mini Autograd 实现","2026-05-05 09:00:00",[1993,2034,1948,2026,2035],"Autograd","Mini Framework",{"slug":2037,"path":1825,"title":5,"date":1820,"tags":2038,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":832},"lock-free-concurrency-notes",[1829,1830,1831,1832,1833],{"slug":2040,"path":2041,"title":2042,"date":2043,"tags":2044,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":158},"agent-memory","\u002Fposts\u002Fagent-memory","Agent 对话记忆化：从原理到实现","2026-04-24",[1855,1935,1842,1873],{"slug":2046,"path":2047,"title":2048,"date":2043,"tags":2049,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":164},"llm-context-compression","\u002Fposts\u002Fllm-context-compression","LLM 上下文五层压缩机制详解",[1855,1935,2050,1873],"上下文压缩",{"slug":2052,"path":2053,"title":2054,"date":2055,"tags":2056,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":288},"cpp-concurrency-basics","\u002Fposts\u002Fcpp-concurrency-basics","C++ 并发编程入门：从数据竞争到线程池","2026-04-15",[1829,1830,1873,1833],{"slug":2058,"path":2059,"title":2060,"date":2061,"tags":2062,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":68},"travel-in-shenzhen","\u002Fposts\u002Ftravel-in-shenzhen","XCPC 深圳游记","2026-04-13",[2063,1829,2064],"XCPC","比赛",{"slug":2066,"path":2067,"title":2068,"date":2069,"tags":2070,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":175},"backend-stack-deep-dive","\u002Fposts\u002Fbackend-stack-deep-dive","后端五件套：FastAPI \u002F Node.js \u002F SQLAlchemy async \u002F PostgreSQL \u002F Docker 面试速通","2026-04-07",[1896,2071,2072,2073,1873],"FastAPI","PostgreSQL","Docker",{"slug":2075,"path":2076,"title":2077,"date":2078,"tags":2079,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":164},"deepscientist-tech-stack","\u002Fposts\u002Fdeepscientist-tech-stack","DeepScientist 技术栈全解析：一个 AI 科研平台的架构设计","2026-04-06",[2080,2071,2081,2072,1873],"全栈","Next.js",{"slug":2083,"path":2084,"title":2085,"date":2078,"tags":2086,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":267},"minicode-source-analysis","\u002Fposts\u002Fminicode-source-analysis","MiniCode 源码解析：用 5000 行 TypeScript 实现一个 AI 编程助手",[1982,2087,1855,1917,1873],"CLI",{"slug":2089,"path":2090,"title":2091,"date":2078,"tags":2092,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":158},"nova-theme-implementation","\u002Fposts\u002Fnova-theme-implementation","我是怎么从零实现 Nova 主题的",[2093,2094,2095],"Hexo","前端","开源",{"slug":2097,"path":2098,"title":2099,"date":2100,"tags":2101,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":110},"git-cheatsheet","\u002Fposts\u002Fgit-cheatsheet","Git 常用操作备忘","2026-04-05 14:00:00",[2102,2103],"Git","工具",{"slug":2105,"path":2106,"title":2107,"date":2108,"tags":2109,"description":61,"draft":1822,"hidden":1822,"published":225,"readingTime":131},"github-actions-intro","\u002Fposts\u002Fgithub-actions-intro","GitHub Actions 入门：自动化你的工作流","2026-04-04 09:00:00",[2110,2111,2112],"GitHub Actions","CI\u002FCD","自动化",1782796012382]