[{"data":1,"prerenderedAt":3282},["ShallowReactive",2],{"content-\u002F03-concurrency\u002F06-lock-free":3},{"id":4,"title":5,"body":6,"description":40,"difficulty":3264,"extension":3265,"meta":3266,"module":3267,"navigation":567,"next":3268,"order":239,"path":3269,"prev":3270,"seo":3271,"slug":3272,"stem":3273,"tags":3274,"__hash__":3281},"content\u002F03-concurrency\u002F06-lock-free\u002Findex.md","Lock-free и алгоритмы синхронизации",{"type":7,"value":8,"toc":3219},"minimark",[9,22,25,30,41,54,58,61,67,71,77,83,89,92,98,100,111,113,117,120,124,294,298,455,459,466,470,477,480,482,497,499,503,506,513,517,703,706,709,970,976,979,1217,1223,1227,1230,1247,1250,1252,1263,1265,1269,1481,1485,1491,1497,1503,1513,1517,1523,1529,1533,1544,1546,1557,1559,1563,1569,1587,1591,1594,1600,1603,1607,1613,1617,1827,1830,1836,1840,1845,1850,1856,1858,1869,1871,1875,1926,1929,1933,2291,2295,2298,2304,2308,2322,2326,2329,2333,2347,2349,2360,2362,2365,2368,2373,2483,2488,2677,2681,2687,2694,2697,2701,2712,2718,2720,2731,2733,2737,2740,2746,2749,2753,2792,2795,2799,2806,2812,2819,2825,2831,2835,2842,2845,2851,2980,2984,2991,2997,3080,3086,3096,3100,3106,3110,3121,3123,3134,3136,3140,3146,3149,3153,3159,3163,3169,3173,3176,3182,3185,3199,3206,3210,3213,3215],[10,11,12,16,19],"ul",{},[13,14,15],"li",{},"ABA-проблема — CAS видит тот же адрес, но данные за ним уже другие. Аллокатор переиспользовал освобождённую память",[13,17,18],{},"В Go не возникает: GC не освободит память пока есть ссылка. Актуально для C\u002FC++ и кастомных аллокаторов",[13,20,21],{},"Решения: hazard pointers, tagged pointers, GC",[23,24],"hr",{},[26,27,29],"h2",{"id":28},"суть-проблемы","Суть проблемы",[31,32,37],"pre",{"className":33,"code":35,"language":36},[34],"language-text","Горутина 1: загрузила head → указывает на элемент A (адрес 0x0A)\n            ... засыпает ...\n\nГорутина 2: Pop(A), Pop(B), Push(C)\n            → аллокатор выделил C по тому же адресу 0x0A (переиспользование)\n\nГорутина 1: просыпается\n            CAS(&head, 0x0A, ...) → УСПЕХ! адрес совпал\n            → но там уже элемент C, не A. Стек сломан.\n","text",[38,39,35],"code",{"__ignoreMap":40},"",[42,43,44,45,49,50,53],"p",{},"CAS сравнивает ",[46,47,48],"strong",{},"адреса",", не ",[46,51,52],{},"содержимое",". Адрес 0x0A = 0x0A, хотя за ним совершенно другие данные.",[26,55,57],{"id":56},"почему-в-go-не-возникает","Почему в Go не возникает",[42,59,60],{},"GC не освободит объект, пока на него есть хоть одна ссылка. Горутина 1 держит указатель на A → A не будет собран → аллокатор не переиспользует этот адрес → CAS корректно обнаружит изменение.",[42,62,63,66],{},[46,64,65],{},"Возникает если",": пишете кастомный аллокатор на Go, или работаете в C\u002FC++ где free() возвращает память немедленно.",[26,68,70],{"id":69},"решения","Решения",[42,72,73,76],{},[46,74,75],{},"Hazard Pointers"," — список \"опасных\" указателей. Если указатель в списке — нельзя освобождать память. Перед обращением — добавляешь в список, после — убираешь.",[42,78,79,82],{},[46,80,81],{},"Tagged Pointers"," — в неиспользуемых битах адреса (например, верхние 16 из 64) хранится счётчик. При каждом обращении счётчик инкрементируется. CAS сравнивает адрес + счётчик → даже если адрес совпал, счётчик не совпадёт.",[31,84,87],{"className":85,"code":86,"language":36},[34],"Адрес без тега:  0x000000000000_0A\nАдрес с тегом:   0x0001_0000000000_0A  (тег=1)\nПосле операции:  0x0002_0000000000_0A  (тег=2) → CAS не пройдёт\n",[38,88,86],{"__ignoreMap":40},[42,90,91],{},"Используется в Boost (C++). Зависит от архитектуры (сколько бит доступно).",[42,93,94,97],{},[46,95,96],{},"GC"," — встроенный (как в Go) или локальный для структуры данных. Некоторые реализации пишут мини-GC специально для одной структуры.",[23,99],{},[10,101,102,105,108],{},[13,103,104],{},"Стек Трайбера — lock-free стек на связанном списке. Push и Pop = один CAS-луп на голове",[13,106,107],{},"Высокий contention: все горутины оперируют одной точкой (головой) → не лучшая структура для конкурентного доступа",[13,109,110],{},"Подвержен ABA-проблеме (в Go защищает GC)",[23,112],{},[26,114,116],{"id":115},"идея","Идея",[42,118,119],{},"Стек = связанный список, операции только с головой. CAS атомарно обновляет указатель на голову.",[26,121,123],{"id":122},"push","Push",[31,125,129],{"className":126,"code":127,"language":128,"meta":40,"style":40},"language-go shiki shiki-themes github-dark","func (s *Stack) Push(val int) {\n    node := &Item{value: val}\n    for {\n        head := atomic.LoadPointer(&s.head)\n        node.next = head                              \u002F\u002F next → старая голова\n        if atomic.CompareAndSwapPointer(&s.head, head, unsafe.Pointer(node)) {\n            return  \u002F\u002F голова обновлена на новый элемент\n        }\n        \u002F\u002F кто-то встроился → повторяем\n    }\n}\n","go",[38,130,131,172,190,199,221,237,261,270,276,282,288],{"__ignoreMap":40},[132,133,136,140,144,148,151,155,158,160,163,166,169],"span",{"class":134,"line":135},"line",1,[132,137,139],{"class":138},"snl16","func",[132,141,143],{"class":142},"s95oV"," (",[132,145,147],{"class":146},"s9osk","s ",[132,149,150],{"class":138},"*",[132,152,154],{"class":153},"svObZ","Stack",[132,156,157],{"class":142},") ",[132,159,123],{"class":153},[132,161,162],{"class":142},"(",[132,164,165],{"class":146},"val",[132,167,168],{"class":138}," int",[132,170,171],{"class":142},") {\n",[132,173,175,178,181,184,187],{"class":134,"line":174},2,[132,176,177],{"class":142},"    node ",[132,179,180],{"class":138},":=",[132,182,183],{"class":138}," &",[132,185,186],{"class":153},"Item",[132,188,189],{"class":142},"{value: val}\n",[132,191,193,196],{"class":134,"line":192},3,[132,194,195],{"class":138},"    for",[132,197,198],{"class":142}," {\n",[132,200,202,205,207,210,213,215,218],{"class":134,"line":201},4,[132,203,204],{"class":142},"        head ",[132,206,180],{"class":138},[132,208,209],{"class":142}," atomic.",[132,211,212],{"class":153},"LoadPointer",[132,214,162],{"class":142},[132,216,217],{"class":138},"&",[132,219,220],{"class":142},"s.head)\n",[132,222,224,227,230,233],{"class":134,"line":223},5,[132,225,226],{"class":142},"        node.next ",[132,228,229],{"class":138},"=",[132,231,232],{"class":142}," head                              ",[132,234,236],{"class":235},"sAwPA","\u002F\u002F next → старая голова\n",[132,238,240,243,245,248,250,252,255,258],{"class":134,"line":239},6,[132,241,242],{"class":138},"        if",[132,244,209],{"class":142},[132,246,247],{"class":153},"CompareAndSwapPointer",[132,249,162],{"class":142},[132,251,217],{"class":138},[132,253,254],{"class":142},"s.head, head, unsafe.",[132,256,257],{"class":153},"Pointer",[132,259,260],{"class":142},"(node)) {\n",[132,262,264,267],{"class":134,"line":263},7,[132,265,266],{"class":138},"            return",[132,268,269],{"class":235},"  \u002F\u002F голова обновлена на новый элемент\n",[132,271,273],{"class":134,"line":272},8,[132,274,275],{"class":142},"        }\n",[132,277,279],{"class":134,"line":278},9,[132,280,281],{"class":235},"        \u002F\u002F кто-то встроился → повторяем\n",[132,283,285],{"class":134,"line":284},10,[132,286,287],{"class":142},"    }\n",[132,289,291],{"class":134,"line":290},11,[132,292,293],{"class":142},"}\n",[26,295,297],{"id":296},"pop","Pop",[31,299,301],{"className":126,"code":300,"language":128,"meta":40,"style":40},"func (s *Stack) Pop() int {\n    for {\n        head := atomic.LoadPointer(&s.head)\n        if head == nil {\n            return -1  \u002F\u002F стек пуст\n        }\n        next := atomic.LoadPointer(&(*Item)(head).next)\n        if atomic.CompareAndSwapPointer(&s.head, head, next) {\n            return (*Item)(head).value  \u002F\u002F голова сдвинута на next\n        }\n        \u002F\u002F кто-то встроился → повторяем\n    }\n}\n",[38,302,303,327,333,349,365,378,382,406,421,437,441,445,450],{"__ignoreMap":40},[132,304,305,307,309,311,313,315,317,319,322,325],{"class":134,"line":135},[132,306,139],{"class":138},[132,308,143],{"class":142},[132,310,147],{"class":146},[132,312,150],{"class":138},[132,314,154],{"class":153},[132,316,157],{"class":142},[132,318,297],{"class":153},[132,320,321],{"class":142},"() ",[132,323,324],{"class":138},"int",[132,326,198],{"class":142},[132,328,329,331],{"class":134,"line":174},[132,330,195],{"class":138},[132,332,198],{"class":142},[132,334,335,337,339,341,343,345,347],{"class":134,"line":192},[132,336,204],{"class":142},[132,338,180],{"class":138},[132,340,209],{"class":142},[132,342,212],{"class":153},[132,344,162],{"class":142},[132,346,217],{"class":138},[132,348,220],{"class":142},[132,350,351,353,356,359,363],{"class":134,"line":201},[132,352,242],{"class":138},[132,354,355],{"class":142}," head ",[132,357,358],{"class":138},"==",[132,360,362],{"class":361},"sDLfK"," nil",[132,364,198],{"class":142},[132,366,367,369,372,375],{"class":134,"line":223},[132,368,266],{"class":138},[132,370,371],{"class":138}," -",[132,373,374],{"class":361},"1",[132,376,377],{"class":235},"  \u002F\u002F стек пуст\n",[132,379,380],{"class":134,"line":239},[132,381,275],{"class":142},[132,383,384,387,389,391,393,395,397,399,401,403],{"class":134,"line":263},[132,385,386],{"class":142},"        next ",[132,388,180],{"class":138},[132,390,209],{"class":142},[132,392,212],{"class":153},[132,394,162],{"class":142},[132,396,217],{"class":138},[132,398,162],{"class":142},[132,400,150],{"class":138},[132,402,186],{"class":153},[132,404,405],{"class":142},")(head).next)\n",[132,407,408,410,412,414,416,418],{"class":134,"line":272},[132,409,242],{"class":138},[132,411,209],{"class":142},[132,413,247],{"class":153},[132,415,162],{"class":142},[132,417,217],{"class":138},[132,419,420],{"class":142},"s.head, head, next) {\n",[132,422,423,425,427,429,431,434],{"class":134,"line":278},[132,424,266],{"class":138},[132,426,143],{"class":142},[132,428,150],{"class":138},[132,430,186],{"class":153},[132,432,433],{"class":142},")(head).value  ",[132,435,436],{"class":235},"\u002F\u002F голова сдвинута на next\n",[132,438,439],{"class":134,"line":284},[132,440,275],{"class":142},[132,442,443],{"class":134,"line":290},[132,444,281],{"class":235},[132,446,448],{"class":134,"line":447},12,[132,449,287],{"class":142},[132,451,453],{"class":134,"line":452},13,[132,454,293],{"class":142},[26,456,458],{"id":457},"почему-cas-а-не-просто-присваивание","Почему CAS, а не просто присваивание",[42,460,461,462,465],{},"Между ",[38,463,464],{},"Load"," и обновлением головы другая горутина может изменить стек. CAS проверяет: \"голова всё ещё та, что я загрузил?\" Если да — обновляем. Если нет — повторяем.",[26,467,469],{"id":468},"проблема-высокий-contention","Проблема: высокий contention",[42,471,472,473,476],{},"У стека ",[46,474,475],{},"одна точка входа и выхода"," — голова. Все горутины конкурируют за неё. Много CAS-неудач → много повторных итераций.",[42,478,479],{},"Очередь лучше: голова (pop) и хвост (push) разнесены → меньше contention.",[23,481],{},[10,483,484,487,494],{},[13,485,486],{},"Очередь Майкла-Скотта — lock-free очередь на связанном списке. Базовая структура для многих lock-free алгоритмов",[13,488,489,490,493],{},"Push = два CAS (tail.next + tail). Между ними другая горутина может ",[46,491,492],{},"помочь"," довернуть второй CAS",[13,495,496],{},"Меньше contention чем стек: push работает с хвостом, pop — с головой",[23,498],{},[26,500,502],{"id":501},"ключевая-идея-помоги-другой-горутине","Ключевая идея: помоги другой горутине",[42,504,505],{},"В стеке один CAS. В очереди — два (обновить tail.next и tail). Между ними может встроиться другая горутина.",[42,507,508,509,512],{},"Решение: если горутина видит что кто-то начал push (tail.next ≠ nil), но не довернул tail — она ",[46,510,511],{},"сама довернёт"," чужой tail, а потом продолжит свой push.",[26,514,516],{"id":515},"структура","Структура",[31,518,520],{"className":126,"code":519,"language":128,"meta":40,"style":40},"type Item struct {\n    value int\n    next  unsafe.Pointer  \u002F\u002F atomic\n}\n\ntype Queue struct {\n    head unsafe.Pointer   \u002F\u002F dummy → first real element\n    tail unsafe.Pointer   \u002F\u002F last element\n}\n\nfunc NewQueue() *Queue {\n    dummy := &Item{}\n    q := &Queue{}\n    q.head = unsafe.Pointer(dummy)\n    q.tail = unsafe.Pointer(dummy)\n    return q\n}\n",[38,521,522,535,543,559,563,569,580,594,608,612,616,632,646,659,675,689,698],{"__ignoreMap":40},[132,523,524,527,530,533],{"class":134,"line":135},[132,525,526],{"class":138},"type",[132,528,529],{"class":153}," Item",[132,531,532],{"class":138}," struct",[132,534,198],{"class":142},[132,536,537,540],{"class":134,"line":174},[132,538,539],{"class":142},"    value ",[132,541,542],{"class":138},"int\n",[132,544,545,548,551,554,556],{"class":134,"line":192},[132,546,547],{"class":142},"    next  ",[132,549,550],{"class":153},"unsafe",[132,552,553],{"class":142},".",[132,555,257],{"class":153},[132,557,558],{"class":235},"  \u002F\u002F atomic\n",[132,560,561],{"class":134,"line":201},[132,562,293],{"class":142},[132,564,565],{"class":134,"line":223},[132,566,568],{"emptyLinePlaceholder":567},true,"\n",[132,570,571,573,576,578],{"class":134,"line":239},[132,572,526],{"class":138},[132,574,575],{"class":153}," Queue",[132,577,532],{"class":138},[132,579,198],{"class":142},[132,581,582,585,587,589,591],{"class":134,"line":263},[132,583,584],{"class":142},"    head ",[132,586,550],{"class":153},[132,588,553],{"class":142},[132,590,257],{"class":153},[132,592,593],{"class":235},"   \u002F\u002F dummy → first real element\n",[132,595,596,599,601,603,605],{"class":134,"line":272},[132,597,598],{"class":142},"    tail ",[132,600,550],{"class":153},[132,602,553],{"class":142},[132,604,257],{"class":153},[132,606,607],{"class":235},"   \u002F\u002F last element\n",[132,609,610],{"class":134,"line":278},[132,611,293],{"class":142},[132,613,614],{"class":134,"line":284},[132,615,568],{"emptyLinePlaceholder":567},[132,617,618,620,623,625,627,630],{"class":134,"line":290},[132,619,139],{"class":138},[132,621,622],{"class":153}," NewQueue",[132,624,321],{"class":142},[132,626,150],{"class":138},[132,628,629],{"class":153},"Queue",[132,631,198],{"class":142},[132,633,634,637,639,641,643],{"class":134,"line":447},[132,635,636],{"class":142},"    dummy ",[132,638,180],{"class":138},[132,640,183],{"class":138},[132,642,186],{"class":153},[132,644,645],{"class":142},"{}\n",[132,647,648,651,653,655,657],{"class":134,"line":452},[132,649,650],{"class":142},"    q ",[132,652,180],{"class":138},[132,654,183],{"class":138},[132,656,629],{"class":153},[132,658,645],{"class":142},[132,660,662,665,667,670,672],{"class":134,"line":661},14,[132,663,664],{"class":142},"    q.head ",[132,666,229],{"class":138},[132,668,669],{"class":142}," unsafe.",[132,671,257],{"class":153},[132,673,674],{"class":142},"(dummy)\n",[132,676,678,681,683,685,687],{"class":134,"line":677},15,[132,679,680],{"class":142},"    q.tail ",[132,682,229],{"class":138},[132,684,669],{"class":142},[132,686,257],{"class":153},[132,688,674],{"class":142},[132,690,692,695],{"class":134,"line":691},16,[132,693,694],{"class":138},"    return",[132,696,697],{"class":142}," q\n",[132,699,701],{"class":134,"line":700},17,[132,702,293],{"class":142},[42,704,705],{},"Dummy-элемент (пустышка) — чтобы не писать лишних if'ов. Head и tail начинают с dummy.",[26,707,123],{"id":708},"push-1",[31,710,712],{"className":126,"code":711,"language":128,"meta":40,"style":40},"func (q *Queue) Push(val int) {\n    node := &Item{value: val}\n    for {\n        tail := atomic.LoadPointer(&q.tail)\n        next := atomic.LoadPointer(&(*Item)(tail).next)\n\n        if tail != atomic.LoadPointer(&q.tail) {\n            continue  \u002F\u002F tail изменился, начинаем заново\n        }\n\n        if next == nil {\n            \u002F\u002F Никто не добавляет → CAS 1: tail.next = node\n            if atomic.CompareAndSwapPointer(&(*Item)(tail).next, nil, unsafe.Pointer(node)) {\n                \u002F\u002F CAS 2: tail = node (не проверяем успех!)\n                atomic.CompareAndSwapPointer(&q.tail, tail, unsafe.Pointer(node))\n                return\n            }\n        } else {\n            \u002F\u002F Кто-то начал push, но не довернул tail → ПОМОГАЕМ\n            atomic.CompareAndSwapPointer(&q.tail, tail, next)\n        }\n    }\n}\n",[38,713,714,739,751,757,775,798,802,823,831,835,839,852,857,889,894,913,918,923,934,940,955,960,965],{"__ignoreMap":40},[132,715,716,718,720,723,725,727,729,731,733,735,737],{"class":134,"line":135},[132,717,139],{"class":138},[132,719,143],{"class":142},[132,721,722],{"class":146},"q ",[132,724,150],{"class":138},[132,726,629],{"class":153},[132,728,157],{"class":142},[132,730,123],{"class":153},[132,732,162],{"class":142},[132,734,165],{"class":146},[132,736,168],{"class":138},[132,738,171],{"class":142},[132,740,741,743,745,747,749],{"class":134,"line":174},[132,742,177],{"class":142},[132,744,180],{"class":138},[132,746,183],{"class":138},[132,748,186],{"class":153},[132,750,189],{"class":142},[132,752,753,755],{"class":134,"line":192},[132,754,195],{"class":138},[132,756,198],{"class":142},[132,758,759,762,764,766,768,770,772],{"class":134,"line":201},[132,760,761],{"class":142},"        tail ",[132,763,180],{"class":138},[132,765,209],{"class":142},[132,767,212],{"class":153},[132,769,162],{"class":142},[132,771,217],{"class":138},[132,773,774],{"class":142},"q.tail)\n",[132,776,777,779,781,783,785,787,789,791,793,795],{"class":134,"line":223},[132,778,386],{"class":142},[132,780,180],{"class":138},[132,782,209],{"class":142},[132,784,212],{"class":153},[132,786,162],{"class":142},[132,788,217],{"class":138},[132,790,162],{"class":142},[132,792,150],{"class":138},[132,794,186],{"class":153},[132,796,797],{"class":142},")(tail).next)\n",[132,799,800],{"class":134,"line":239},[132,801,568],{"emptyLinePlaceholder":567},[132,803,804,806,809,812,814,816,818,820],{"class":134,"line":263},[132,805,242],{"class":138},[132,807,808],{"class":142}," tail ",[132,810,811],{"class":138},"!=",[132,813,209],{"class":142},[132,815,212],{"class":153},[132,817,162],{"class":142},[132,819,217],{"class":138},[132,821,822],{"class":142},"q.tail) {\n",[132,824,825,828],{"class":134,"line":272},[132,826,827],{"class":138},"            continue",[132,829,830],{"class":235},"  \u002F\u002F tail изменился, начинаем заново\n",[132,832,833],{"class":134,"line":278},[132,834,275],{"class":142},[132,836,837],{"class":134,"line":284},[132,838,568],{"emptyLinePlaceholder":567},[132,840,841,843,846,848,850],{"class":134,"line":290},[132,842,242],{"class":138},[132,844,845],{"class":142}," next ",[132,847,358],{"class":138},[132,849,362],{"class":361},[132,851,198],{"class":142},[132,853,854],{"class":134,"line":447},[132,855,856],{"class":235},"            \u002F\u002F Никто не добавляет → CAS 1: tail.next = node\n",[132,858,859,862,864,866,868,870,872,874,876,879,882,885,887],{"class":134,"line":452},[132,860,861],{"class":138},"            if",[132,863,209],{"class":142},[132,865,247],{"class":153},[132,867,162],{"class":142},[132,869,217],{"class":138},[132,871,162],{"class":142},[132,873,150],{"class":138},[132,875,186],{"class":153},[132,877,878],{"class":142},")(tail).next, ",[132,880,881],{"class":361},"nil",[132,883,884],{"class":142},", unsafe.",[132,886,257],{"class":153},[132,888,260],{"class":142},[132,890,891],{"class":134,"line":661},[132,892,893],{"class":235},"                \u002F\u002F CAS 2: tail = node (не проверяем успех!)\n",[132,895,896,899,901,903,905,908,910],{"class":134,"line":677},[132,897,898],{"class":142},"                atomic.",[132,900,247],{"class":153},[132,902,162],{"class":142},[132,904,217],{"class":138},[132,906,907],{"class":142},"q.tail, tail, unsafe.",[132,909,257],{"class":153},[132,911,912],{"class":142},"(node))\n",[132,914,915],{"class":134,"line":691},[132,916,917],{"class":138},"                return\n",[132,919,920],{"class":134,"line":700},[132,921,922],{"class":142},"            }\n",[132,924,926,929,932],{"class":134,"line":925},18,[132,927,928],{"class":142},"        } ",[132,930,931],{"class":138},"else",[132,933,198],{"class":142},[132,935,937],{"class":134,"line":936},19,[132,938,939],{"class":235},"            \u002F\u002F Кто-то начал push, но не довернул tail → ПОМОГАЕМ\n",[132,941,943,946,948,950,952],{"class":134,"line":942},20,[132,944,945],{"class":142},"            atomic.",[132,947,247],{"class":153},[132,949,162],{"class":142},[132,951,217],{"class":138},[132,953,954],{"class":142},"q.tail, tail, next)\n",[132,956,958],{"class":134,"line":957},21,[132,959,275],{"class":142},[132,961,963],{"class":134,"line":962},22,[132,964,287],{"class":142},[132,966,968],{"class":134,"line":967},23,[132,969,293],{"class":142},[42,971,972,975],{},[46,973,974],{},"CAS 2 не проверяется",": если не получилось — другая горутина поможет (ветка else).",[26,977,297],{"id":978},"pop-1",[31,980,982],{"className":126,"code":981,"language":128,"meta":40,"style":40},"func (q *Queue) Pop() int {\n    for {\n        head := atomic.LoadPointer(&q.head)\n        tail := atomic.LoadPointer(&q.tail)\n        next := atomic.LoadPointer(&(*Item)(head).next)\n\n        if head != atomic.LoadPointer(&q.head) { continue }\n\n        if head == tail {\n            if next == nil { return -1 }       \u002F\u002F пусто\n            \u002F\u002F Один элемент + кто-то пушит → помогаем довернуть tail\n            atomic.CompareAndSwapPointer(&q.tail, tail, next)\n        } else {\n            val := (*Item)(next).value\n            if atomic.CompareAndSwapPointer(&q.head, head, next) {\n                return val\n            }\n        }\n    }\n}\n",[38,983,984,1006,1012,1029,1045,1067,1071,1096,1100,1111,1137,1142,1154,1162,1178,1193,1201,1205,1209,1213],{"__ignoreMap":40},[132,985,986,988,990,992,994,996,998,1000,1002,1004],{"class":134,"line":135},[132,987,139],{"class":138},[132,989,143],{"class":142},[132,991,722],{"class":146},[132,993,150],{"class":138},[132,995,629],{"class":153},[132,997,157],{"class":142},[132,999,297],{"class":153},[132,1001,321],{"class":142},[132,1003,324],{"class":138},[132,1005,198],{"class":142},[132,1007,1008,1010],{"class":134,"line":174},[132,1009,195],{"class":138},[132,1011,198],{"class":142},[132,1013,1014,1016,1018,1020,1022,1024,1026],{"class":134,"line":192},[132,1015,204],{"class":142},[132,1017,180],{"class":138},[132,1019,209],{"class":142},[132,1021,212],{"class":153},[132,1023,162],{"class":142},[132,1025,217],{"class":138},[132,1027,1028],{"class":142},"q.head)\n",[132,1030,1031,1033,1035,1037,1039,1041,1043],{"class":134,"line":201},[132,1032,761],{"class":142},[132,1034,180],{"class":138},[132,1036,209],{"class":142},[132,1038,212],{"class":153},[132,1040,162],{"class":142},[132,1042,217],{"class":138},[132,1044,774],{"class":142},[132,1046,1047,1049,1051,1053,1055,1057,1059,1061,1063,1065],{"class":134,"line":223},[132,1048,386],{"class":142},[132,1050,180],{"class":138},[132,1052,209],{"class":142},[132,1054,212],{"class":153},[132,1056,162],{"class":142},[132,1058,217],{"class":138},[132,1060,162],{"class":142},[132,1062,150],{"class":138},[132,1064,186],{"class":153},[132,1066,405],{"class":142},[132,1068,1069],{"class":134,"line":239},[132,1070,568],{"emptyLinePlaceholder":567},[132,1072,1073,1075,1077,1079,1081,1083,1085,1087,1090,1093],{"class":134,"line":263},[132,1074,242],{"class":138},[132,1076,355],{"class":142},[132,1078,811],{"class":138},[132,1080,209],{"class":142},[132,1082,212],{"class":153},[132,1084,162],{"class":142},[132,1086,217],{"class":138},[132,1088,1089],{"class":142},"q.head) { ",[132,1091,1092],{"class":138},"continue",[132,1094,1095],{"class":142}," }\n",[132,1097,1098],{"class":134,"line":272},[132,1099,568],{"emptyLinePlaceholder":567},[132,1101,1102,1104,1106,1108],{"class":134,"line":278},[132,1103,242],{"class":138},[132,1105,355],{"class":142},[132,1107,358],{"class":138},[132,1109,1110],{"class":142}," tail {\n",[132,1112,1113,1115,1117,1119,1121,1124,1127,1129,1131,1134],{"class":134,"line":284},[132,1114,861],{"class":138},[132,1116,845],{"class":142},[132,1118,358],{"class":138},[132,1120,362],{"class":361},[132,1122,1123],{"class":142}," { ",[132,1125,1126],{"class":138},"return",[132,1128,371],{"class":138},[132,1130,374],{"class":361},[132,1132,1133],{"class":142}," }       ",[132,1135,1136],{"class":235},"\u002F\u002F пусто\n",[132,1138,1139],{"class":134,"line":290},[132,1140,1141],{"class":235},"            \u002F\u002F Один элемент + кто-то пушит → помогаем довернуть tail\n",[132,1143,1144,1146,1148,1150,1152],{"class":134,"line":447},[132,1145,945],{"class":142},[132,1147,247],{"class":153},[132,1149,162],{"class":142},[132,1151,217],{"class":138},[132,1153,954],{"class":142},[132,1155,1156,1158,1160],{"class":134,"line":452},[132,1157,928],{"class":142},[132,1159,931],{"class":138},[132,1161,198],{"class":142},[132,1163,1164,1167,1169,1171,1173,1175],{"class":134,"line":661},[132,1165,1166],{"class":142},"            val ",[132,1168,180],{"class":138},[132,1170,143],{"class":142},[132,1172,150],{"class":138},[132,1174,186],{"class":153},[132,1176,1177],{"class":142},")(next).value\n",[132,1179,1180,1182,1184,1186,1188,1190],{"class":134,"line":677},[132,1181,861],{"class":138},[132,1183,209],{"class":142},[132,1185,247],{"class":153},[132,1187,162],{"class":142},[132,1189,217],{"class":138},[132,1191,1192],{"class":142},"q.head, head, next) {\n",[132,1194,1195,1198],{"class":134,"line":691},[132,1196,1197],{"class":138},"                return",[132,1199,1200],{"class":142}," val\n",[132,1202,1203],{"class":134,"line":700},[132,1204,922],{"class":142},[132,1206,1207],{"class":134,"line":925},[132,1208,275],{"class":142},[132,1210,1211],{"class":134,"line":936},[132,1212,287],{"class":142},[132,1214,1215],{"class":134,"line":942},[132,1216,293],{"class":142},[42,1218,1219,1222],{},[46,1220,1221],{},"Pop тоже помогает push",": если head == tail но next ≠ nil — элемент добавлен, но tail не обновлён. Pop доворачивает tail за чужую горутину.",[26,1224,1226],{"id":1225},"два-cas-общий-паттерн","Два CAS — общий паттерн",[42,1228,1229],{},"Когда операция требует >1 CAS, используй подход:",[1231,1232,1233,1240],"ol",{},[13,1234,1235,1236,1239],{},"Каждый CAS после первого — ",[46,1237,1238],{},"не проверяй"," успех",[13,1241,1242,1243,1246],{},"Другие горутины должны ",[46,1244,1245],{},"помогать"," довернуть незавершённые CAS",[42,1248,1249],{},"Это основа для любых lock-free структур сложнее стека.",[23,1251],{},[10,1253,1254,1257,1260],{},[13,1255,1256],{},"Акторная модель — математическая модель параллельных вычислений (1973, Карл Хьюит). Акторы общаются сообщениями, не разделяют состояние",[13,1258,1259],{},"Актор: inbox (буферизированный канал), внутреннее состояние (изолировано), уникальный адрес. 3 действия: создать актор, отправить сообщение, изменить своё состояние",[13,1261,1262],{},"В Go: актор = горутина + канал. Круто для распределённых систем (YDB, Erlang), избыточно для продуктовой бизнес-логики",[23,1264],{},[26,1266,1268],{"id":1267},"структура-актора","Структура актора",[31,1270,1272],{"className":126,"code":1271,"language":128,"meta":40,"style":40},"type Actor struct {\n    inbox    chan Letter           \u002F\u002F почтовый ящик (буферизированный)\n    executor Executor              \u002F\u002F поведение (интерфейс)\n}\n\ntype Letter struct {\n    To   string\n    From string\n    Body interface{}\n}\n\nfunc NewActor(exec Executor) *Actor {\n    a := &Actor{inbox: make(chan Letter, 10), executor: exec}\n    go func() {\n        for letter := range a.inbox {   \u002F\u002F одна горутина обрабатывает\n            a.executor.Execute(letter)   \u002F\u002F последовательно (FIFO)\n        }\n    }()\n    return a\n}\n",[38,1273,1274,1285,1299,1310,1314,1318,1328,1336,1343,1353,1357,1361,1385,1417,1428,1447,1461,1465,1470,1477],{"__ignoreMap":40},[132,1275,1276,1278,1281,1283],{"class":134,"line":135},[132,1277,526],{"class":138},[132,1279,1280],{"class":153}," Actor",[132,1282,532],{"class":138},[132,1284,198],{"class":142},[132,1286,1287,1290,1293,1296],{"class":134,"line":174},[132,1288,1289],{"class":142},"    inbox    ",[132,1291,1292],{"class":138},"chan",[132,1294,1295],{"class":153}," Letter",[132,1297,1298],{"class":235},"           \u002F\u002F почтовый ящик (буферизированный)\n",[132,1300,1301,1304,1307],{"class":134,"line":192},[132,1302,1303],{"class":142},"    executor ",[132,1305,1306],{"class":153},"Executor",[132,1308,1309],{"class":235},"              \u002F\u002F поведение (интерфейс)\n",[132,1311,1312],{"class":134,"line":201},[132,1313,293],{"class":142},[132,1315,1316],{"class":134,"line":223},[132,1317,568],{"emptyLinePlaceholder":567},[132,1319,1320,1322,1324,1326],{"class":134,"line":239},[132,1321,526],{"class":138},[132,1323,1295],{"class":153},[132,1325,532],{"class":138},[132,1327,198],{"class":142},[132,1329,1330,1333],{"class":134,"line":263},[132,1331,1332],{"class":142},"    To   ",[132,1334,1335],{"class":138},"string\n",[132,1337,1338,1341],{"class":134,"line":272},[132,1339,1340],{"class":142},"    From ",[132,1342,1335],{"class":138},[132,1344,1345,1348,1351],{"class":134,"line":278},[132,1346,1347],{"class":142},"    Body ",[132,1349,1350],{"class":138},"interface",[132,1352,645],{"class":142},[132,1354,1355],{"class":134,"line":284},[132,1356,293],{"class":142},[132,1358,1359],{"class":134,"line":290},[132,1360,568],{"emptyLinePlaceholder":567},[132,1362,1363,1365,1368,1370,1373,1376,1378,1380,1383],{"class":134,"line":447},[132,1364,139],{"class":138},[132,1366,1367],{"class":153}," NewActor",[132,1369,162],{"class":142},[132,1371,1372],{"class":146},"exec",[132,1374,1375],{"class":153}," Executor",[132,1377,157],{"class":142},[132,1379,150],{"class":138},[132,1381,1382],{"class":153},"Actor",[132,1384,198],{"class":142},[132,1386,1387,1390,1392,1394,1396,1399,1402,1404,1406,1408,1411,1414],{"class":134,"line":452},[132,1388,1389],{"class":142},"    a ",[132,1391,180],{"class":138},[132,1393,183],{"class":138},[132,1395,1382],{"class":153},[132,1397,1398],{"class":142},"{inbox: ",[132,1400,1401],{"class":153},"make",[132,1403,162],{"class":142},[132,1405,1292],{"class":138},[132,1407,1295],{"class":153},[132,1409,1410],{"class":142},", ",[132,1412,1413],{"class":361},"10",[132,1415,1416],{"class":142},"), executor: exec}\n",[132,1418,1419,1422,1425],{"class":134,"line":661},[132,1420,1421],{"class":138},"    go",[132,1423,1424],{"class":138}," func",[132,1426,1427],{"class":142},"() {\n",[132,1429,1430,1433,1436,1438,1441,1444],{"class":134,"line":677},[132,1431,1432],{"class":138},"        for",[132,1434,1435],{"class":142}," letter ",[132,1437,180],{"class":138},[132,1439,1440],{"class":138}," range",[132,1442,1443],{"class":142}," a.inbox {   ",[132,1445,1446],{"class":235},"\u002F\u002F одна горутина обрабатывает\n",[132,1448,1449,1452,1455,1458],{"class":134,"line":691},[132,1450,1451],{"class":142},"            a.executor.",[132,1453,1454],{"class":153},"Execute",[132,1456,1457],{"class":142},"(letter)   ",[132,1459,1460],{"class":235},"\u002F\u002F последовательно (FIFO)\n",[132,1462,1463],{"class":134,"line":700},[132,1464,275],{"class":142},[132,1466,1467],{"class":134,"line":925},[132,1468,1469],{"class":142},"    }()\n",[132,1471,1472,1474],{"class":134,"line":936},[132,1473,694],{"class":138},[132,1475,1476],{"class":142}," a\n",[132,1478,1479],{"class":134,"line":942},[132,1480,293],{"class":142},[26,1482,1484],{"id":1483},"свойства","Свойства",[42,1486,1487,1490],{},[46,1488,1489],{},"Изоляция",": актор не может изменить состояние другого актора. Только отправить сообщение.",[42,1492,1493,1496],{},[46,1494,1495],{},"Асинхронность",": отправка = запись в буферизированный канал. Обработка — по одному, FIFO.",[42,1498,1499,1502],{},[46,1500,1501],{},"Параллелизм",": нужно обработать N сообщений параллельно → создать N акторов одного типа.",[42,1504,1505,1508,1509,1512],{},[46,1506,1507],{},"Actor Manager",": синглтон, хранит мапу ",[38,1510,1511],{},"адрес → актор",". Маршрутизирует сообщения. Под мьютексом.",[26,1514,1516],{"id":1515},"где-использовать","Где использовать",[42,1518,1519,1522],{},[46,1520,1521],{},"Да",": распределённые системы. Актор сегодня локальный (канал), завтра — удалённый (gRPC). YDB (C++), мониторинг Яндекса (Java), Erlang\u002FOTP.",[42,1524,1525,1528],{},[46,1526,1527],{},"Нет",": обычная продуктовая бизнес-логика. Избыточная абстракция, усложняет код.",[26,1530,1532],{"id":1531},"минусы","Минусы",[10,1534,1535,1538,1541],{},[13,1536,1537],{},"Как получать ошибки? Обратный канал? Две очереди (запрос\u002Fответ)?",[13,1539,1540],{},"Отладка сложнее: асинхронные цепочки вместо синхронных вызовов",[13,1542,1543],{},"Overhead на сериализацию\u002Fдесериализацию сообщений",[23,1545],{},[10,1547,1548,1551,1554],{},[13,1549,1550],{},"Закон Амдала: ускорение программы ограничено последовательной частью. S(N) = 1 \u002F ((1-P) + P\u002FN)",[13,1552,1553],{},"P = доля параллелизуемого кода, N = количество ядер. Даже бесконечное N не поможет если (1-P) > 0",[13,1555,1556],{},"Практическое применение: перед параллелизацией — оцени P, посчитай ожидаемое ускорение, сравни с бенчмарком",[23,1558],{},[26,1560,1562],{"id":1561},"формула","Формула",[31,1564,1567],{"className":1565,"code":1566,"language":36},[34],"           1\nS(N) = -----------\n       (1-P) + P\u002FN\n",[38,1568,1566],{"__ignoreMap":40},[42,1570,1571,1574,1575,1578,1579,1582,1583,1586],{},[46,1572,1573],{},"P"," — доля кода, которую можно распараллелить (0..1)\n",[46,1576,1577],{},"N"," — количество ядер (фактор параллельности)\n",[46,1580,1581],{},"1-P"," — доля кода, которая остаётся последовательной\n",[46,1584,1585],{},"S(N)"," — итоговое ускорение",[26,1588,1590],{"id":1589},"пример","Пример",[42,1592,1593],{},"90% кода параллелится (P=0.9), 10 ядер (N=10):",[31,1595,1598],{"className":1596,"code":1597,"language":36},[34],"S(10) = 1 \u002F (0.1 + 0.9\u002F10) = 1 \u002F (0.1 + 0.09) = 1 \u002F 0.19 = 5.26x\n",[38,1599,1597],{"__ignoreMap":40},[42,1601,1602],{},"Ожидали 10x, получили 5.26x. 10% последовательного кода съели половину ускорения.",[26,1604,1606],{"id":1605},"предел-при-n-бесконечности","Предел при N -> бесконечности",[31,1608,1611],{"className":1609,"code":1610,"language":36},[34],"S(inf) = 1 \u002F (1-P)\n\nP=0.5 -> максимум 2x (сколько бы ядер ни было)\nP=0.9 -> максимум 10x\nP=0.99 -> максимум 100x\n",[38,1612,1610],{"__ignoreMap":40},[26,1614,1616],{"id":1615},"практика-бенчмарк-подтверждает","Практика: бенчмарк подтверждает",[31,1618,1620],{"className":126,"code":1619,"language":128,"meta":40,"style":40},"func calculate(parallelFactor int) {\n    \u002F\u002F 50% последовательный код\n    for i := 0; i \u003C 1_000_000; i++ { \u002F* work *\u002F }\n\n    \u002F\u002F 50% параллелизуемый код\n    var wg sync.WaitGroup\n    chunk := 1_000_000 \u002F parallelFactor\n    for i := 0; i \u003C parallelFactor; i++ {\n        wg.Add(1)\n        go func() {\n            defer wg.Done()\n            for j := 0; j \u003C chunk; j++ { \u002F* work *\u002F }\n        }()\n    }\n    wg.Wait()\n}\n",[38,1621,1622,1638,1643,1677,1681,1686,1702,1717,1738,1753,1762,1776,1804,1809,1813,1823],{"__ignoreMap":40},[132,1623,1624,1626,1629,1631,1634,1636],{"class":134,"line":135},[132,1625,139],{"class":138},[132,1627,1628],{"class":153}," calculate",[132,1630,162],{"class":142},[132,1632,1633],{"class":146},"parallelFactor",[132,1635,168],{"class":138},[132,1637,171],{"class":142},[132,1639,1640],{"class":134,"line":174},[132,1641,1642],{"class":235},"    \u002F\u002F 50% последовательный код\n",[132,1644,1645,1647,1650,1652,1655,1658,1661,1664,1667,1670,1672,1675],{"class":134,"line":192},[132,1646,195],{"class":138},[132,1648,1649],{"class":142}," i ",[132,1651,180],{"class":138},[132,1653,1654],{"class":361}," 0",[132,1656,1657],{"class":142},"; i ",[132,1659,1660],{"class":138},"\u003C",[132,1662,1663],{"class":361}," 1_000_000",[132,1665,1666],{"class":142},"; i",[132,1668,1669],{"class":138},"++",[132,1671,1123],{"class":142},[132,1673,1674],{"class":235},"\u002F* work *\u002F",[132,1676,1095],{"class":142},[132,1678,1679],{"class":134,"line":201},[132,1680,568],{"emptyLinePlaceholder":567},[132,1682,1683],{"class":134,"line":223},[132,1684,1685],{"class":235},"    \u002F\u002F 50% параллелизуемый код\n",[132,1687,1688,1691,1694,1697,1699],{"class":134,"line":239},[132,1689,1690],{"class":138},"    var",[132,1692,1693],{"class":142}," wg ",[132,1695,1696],{"class":153},"sync",[132,1698,553],{"class":142},[132,1700,1701],{"class":153},"WaitGroup\n",[132,1703,1704,1707,1709,1711,1714],{"class":134,"line":263},[132,1705,1706],{"class":142},"    chunk ",[132,1708,180],{"class":138},[132,1710,1663],{"class":361},[132,1712,1713],{"class":138}," \u002F",[132,1715,1716],{"class":142}," parallelFactor\n",[132,1718,1719,1721,1723,1725,1727,1729,1731,1734,1736],{"class":134,"line":272},[132,1720,195],{"class":138},[132,1722,1649],{"class":142},[132,1724,180],{"class":138},[132,1726,1654],{"class":361},[132,1728,1657],{"class":142},[132,1730,1660],{"class":138},[132,1732,1733],{"class":142}," parallelFactor; i",[132,1735,1669],{"class":138},[132,1737,198],{"class":142},[132,1739,1740,1743,1746,1748,1750],{"class":134,"line":278},[132,1741,1742],{"class":142},"        wg.",[132,1744,1745],{"class":153},"Add",[132,1747,162],{"class":142},[132,1749,374],{"class":361},[132,1751,1752],{"class":142},")\n",[132,1754,1755,1758,1760],{"class":134,"line":284},[132,1756,1757],{"class":138},"        go",[132,1759,1424],{"class":138},[132,1761,1427],{"class":142},[132,1763,1764,1767,1770,1773],{"class":134,"line":290},[132,1765,1766],{"class":138},"            defer",[132,1768,1769],{"class":142}," wg.",[132,1771,1772],{"class":153},"Done",[132,1774,1775],{"class":142},"()\n",[132,1777,1778,1781,1784,1786,1788,1791,1793,1796,1798,1800,1802],{"class":134,"line":447},[132,1779,1780],{"class":138},"            for",[132,1782,1783],{"class":142}," j ",[132,1785,180],{"class":138},[132,1787,1654],{"class":361},[132,1789,1790],{"class":142},"; j ",[132,1792,1660],{"class":138},[132,1794,1795],{"class":142}," chunk; j",[132,1797,1669],{"class":138},[132,1799,1123],{"class":142},[132,1801,1674],{"class":235},[132,1803,1095],{"class":142},[132,1805,1806],{"class":134,"line":452},[132,1807,1808],{"class":142},"        }()\n",[132,1810,1811],{"class":134,"line":661},[132,1812,287],{"class":142},[132,1814,1815,1818,1821],{"class":134,"line":677},[132,1816,1817],{"class":142},"    wg.",[132,1819,1820],{"class":153},"Wait",[132,1822,1775],{"class":142},[132,1824,1825],{"class":134,"line":691},[132,1826,293],{"class":142},[42,1828,1829],{},"P=0.5:",[31,1831,1834],{"className":1832,"code":1833,"language":36},[34],"N=1:  базовое время T\nN=2:  S = 1\u002F(0.5 + 0.25) = 1.33x  -> T\u002F1.33 (бенчмарк подтверждает)\nN=4:  S = 1\u002F(0.5 + 0.125) = 1.6x  -> T\u002F1.6  (бенчмарк подтверждает)\n",[38,1835,1833],{"__ignoreMap":40},[26,1837,1839],{"id":1838},"когда-применять","Когда применять",[42,1841,1842,1844],{},[46,1843,1521],{},": небольшая функция, можно глазами оценить P. Перед решением о параллелизации — посчитай.",[42,1846,1847,1849],{},[46,1848,1527],{},": огромная кодовая база, трудно оценить P.",[42,1851,1852,1855],{},[46,1853,1854],{},"Вывод",": если последовательная часть большая — параллелизация не поможет. Сначала оптимизируй последовательную часть.",[23,1857],{},[10,1859,1860,1863,1866],{},[13,1861,1862],{},"Шардированная мапа — разрезать одну мапу с одним мьютексом на N мап с N мьютексами",[13,1864,1865],{},"Роутинг по шардам: hash(key) % N. Горутины расходятся по разным шардам → меньше contention",[13,1867,1868],{},"Тот же принцип что локальные очереди в планировщике Go",[23,1870],{},[26,1872,1874],{"id":1873},"проблема","Проблема",[31,1876,1878],{"className":126,"code":1877,"language":128,"meta":40,"style":40},"type Cache struct {\n    mu   sync.Mutex\n    data map[string]string\n}\n",[38,1879,1880,1891,1903,1922],{"__ignoreMap":40},[132,1881,1882,1884,1887,1889],{"class":134,"line":135},[132,1883,526],{"class":138},[132,1885,1886],{"class":153}," Cache",[132,1888,532],{"class":138},[132,1890,198],{"class":142},[132,1892,1893,1896,1898,1900],{"class":134,"line":174},[132,1894,1895],{"class":142},"    mu   ",[132,1897,1696],{"class":153},[132,1899,553],{"class":142},[132,1901,1902],{"class":153},"Mutex\n",[132,1904,1905,1908,1911,1914,1917,1920],{"class":134,"line":192},[132,1906,1907],{"class":142},"    data ",[132,1909,1910],{"class":138},"map",[132,1912,1913],{"class":142},"[",[132,1915,1916],{"class":138},"string",[132,1918,1919],{"class":142},"]",[132,1921,1335],{"class":138},[132,1923,1924],{"class":134,"line":201},[132,1925,293],{"class":142},[42,1927,1928],{},"Один мьютекс → все горутины конкурируют за него → bottleneck.",[26,1930,1932],{"id":1931},"решение-шардирование","Решение: шардирование",[31,1934,1936],{"className":126,"code":1935,"language":128,"meta":40,"style":40},"type Shard struct {\n    mu   sync.RWMutex\n    data map[string]string\n}\n\ntype ShardedMap struct {\n    shards []Shard\n}\n\nfunc NewShardedMap(n int) *ShardedMap {\n    sm := &ShardedMap{shards: make([]Shard, n)}\n    for i := range sm.shards {\n        sm.shards[i].data = make(map[string]string)\n    }\n    return sm\n}\n\nfunc (sm *ShardedMap) getShard(key string) *Shard {\n    h := fnv32(key)\n    return &sm.shards[h%uint32(len(sm.shards))]\n}\n\nfunc (sm *ShardedMap) Get(key string) (string, bool) {\n    shard := sm.getShard(key)  \u002F\u002F без синхронизации — слайс не меняется\n    shard.mu.RLock()\n    defer shard.mu.RUnlock()\n    v, ok := shard.data[key]\n    return v, ok\n}\n",[38,1937,1938,1949,1960,1974,1978,1982,1993,2001,2005,2009,2032,2057,2070,2094,2098,2105,2109,2113,2147,2160,2180,2184,2188,2223,2242,2253,2267,2278,2286],{"__ignoreMap":40},[132,1939,1940,1942,1945,1947],{"class":134,"line":135},[132,1941,526],{"class":138},[132,1943,1944],{"class":153}," Shard",[132,1946,532],{"class":138},[132,1948,198],{"class":142},[132,1950,1951,1953,1955,1957],{"class":134,"line":174},[132,1952,1895],{"class":142},[132,1954,1696],{"class":153},[132,1956,553],{"class":142},[132,1958,1959],{"class":153},"RWMutex\n",[132,1961,1962,1964,1966,1968,1970,1972],{"class":134,"line":192},[132,1963,1907],{"class":142},[132,1965,1910],{"class":138},[132,1967,1913],{"class":142},[132,1969,1916],{"class":138},[132,1971,1919],{"class":142},[132,1973,1335],{"class":138},[132,1975,1976],{"class":134,"line":201},[132,1977,293],{"class":142},[132,1979,1980],{"class":134,"line":223},[132,1981,568],{"emptyLinePlaceholder":567},[132,1983,1984,1986,1989,1991],{"class":134,"line":239},[132,1985,526],{"class":138},[132,1987,1988],{"class":153}," ShardedMap",[132,1990,532],{"class":138},[132,1992,198],{"class":142},[132,1994,1995,1998],{"class":134,"line":263},[132,1996,1997],{"class":142},"    shards []",[132,1999,2000],{"class":153},"Shard\n",[132,2002,2003],{"class":134,"line":272},[132,2004,293],{"class":142},[132,2006,2007],{"class":134,"line":278},[132,2008,568],{"emptyLinePlaceholder":567},[132,2010,2011,2013,2016,2018,2021,2023,2025,2027,2030],{"class":134,"line":284},[132,2012,139],{"class":138},[132,2014,2015],{"class":153}," NewShardedMap",[132,2017,162],{"class":142},[132,2019,2020],{"class":146},"n",[132,2022,168],{"class":138},[132,2024,157],{"class":142},[132,2026,150],{"class":138},[132,2028,2029],{"class":153},"ShardedMap",[132,2031,198],{"class":142},[132,2033,2034,2037,2039,2041,2043,2046,2048,2051,2054],{"class":134,"line":290},[132,2035,2036],{"class":142},"    sm ",[132,2038,180],{"class":138},[132,2040,183],{"class":138},[132,2042,2029],{"class":153},[132,2044,2045],{"class":142},"{shards: ",[132,2047,1401],{"class":153},[132,2049,2050],{"class":142},"([]",[132,2052,2053],{"class":153},"Shard",[132,2055,2056],{"class":142},", n)}\n",[132,2058,2059,2061,2063,2065,2067],{"class":134,"line":447},[132,2060,195],{"class":138},[132,2062,1649],{"class":142},[132,2064,180],{"class":138},[132,2066,1440],{"class":138},[132,2068,2069],{"class":142}," sm.shards {\n",[132,2071,2072,2075,2077,2080,2082,2084,2086,2088,2090,2092],{"class":134,"line":452},[132,2073,2074],{"class":142},"        sm.shards[i].data ",[132,2076,229],{"class":138},[132,2078,2079],{"class":153}," make",[132,2081,162],{"class":142},[132,2083,1910],{"class":138},[132,2085,1913],{"class":142},[132,2087,1916],{"class":138},[132,2089,1919],{"class":142},[132,2091,1916],{"class":138},[132,2093,1752],{"class":142},[132,2095,2096],{"class":134,"line":661},[132,2097,287],{"class":142},[132,2099,2100,2102],{"class":134,"line":677},[132,2101,694],{"class":138},[132,2103,2104],{"class":142}," sm\n",[132,2106,2107],{"class":134,"line":691},[132,2108,293],{"class":142},[132,2110,2111],{"class":134,"line":700},[132,2112,568],{"emptyLinePlaceholder":567},[132,2114,2115,2117,2119,2122,2124,2126,2128,2131,2133,2136,2139,2141,2143,2145],{"class":134,"line":925},[132,2116,139],{"class":138},[132,2118,143],{"class":142},[132,2120,2121],{"class":146},"sm ",[132,2123,150],{"class":138},[132,2125,2029],{"class":153},[132,2127,157],{"class":142},[132,2129,2130],{"class":153},"getShard",[132,2132,162],{"class":142},[132,2134,2135],{"class":146},"key",[132,2137,2138],{"class":138}," string",[132,2140,157],{"class":142},[132,2142,150],{"class":138},[132,2144,2053],{"class":153},[132,2146,198],{"class":142},[132,2148,2149,2152,2154,2157],{"class":134,"line":936},[132,2150,2151],{"class":142},"    h ",[132,2153,180],{"class":138},[132,2155,2156],{"class":153}," fnv32",[132,2158,2159],{"class":142},"(key)\n",[132,2161,2162,2164,2166,2169,2172,2174,2177],{"class":134,"line":942},[132,2163,694],{"class":138},[132,2165,183],{"class":138},[132,2167,2168],{"class":142},"sm.shards[h",[132,2170,2171],{"class":138},"%uint32",[132,2173,162],{"class":142},[132,2175,2176],{"class":153},"len",[132,2178,2179],{"class":142},"(sm.shards))]\n",[132,2181,2182],{"class":134,"line":957},[132,2183,293],{"class":142},[132,2185,2186],{"class":134,"line":962},[132,2187,568],{"emptyLinePlaceholder":567},[132,2189,2190,2192,2194,2196,2198,2200,2202,2205,2207,2209,2211,2214,2216,2218,2221],{"class":134,"line":967},[132,2191,139],{"class":138},[132,2193,143],{"class":142},[132,2195,2121],{"class":146},[132,2197,150],{"class":138},[132,2199,2029],{"class":153},[132,2201,157],{"class":142},[132,2203,2204],{"class":153},"Get",[132,2206,162],{"class":142},[132,2208,2135],{"class":146},[132,2210,2138],{"class":138},[132,2212,2213],{"class":142},") (",[132,2215,1916],{"class":138},[132,2217,1410],{"class":142},[132,2219,2220],{"class":138},"bool",[132,2222,171],{"class":142},[132,2224,2226,2229,2231,2234,2236,2239],{"class":134,"line":2225},24,[132,2227,2228],{"class":142},"    shard ",[132,2230,180],{"class":138},[132,2232,2233],{"class":142}," sm.",[132,2235,2130],{"class":153},[132,2237,2238],{"class":142},"(key)  ",[132,2240,2241],{"class":235},"\u002F\u002F без синхронизации — слайс не меняется\n",[132,2243,2245,2248,2251],{"class":134,"line":2244},25,[132,2246,2247],{"class":142},"    shard.mu.",[132,2249,2250],{"class":153},"RLock",[132,2252,1775],{"class":142},[132,2254,2256,2259,2262,2265],{"class":134,"line":2255},26,[132,2257,2258],{"class":138},"    defer",[132,2260,2261],{"class":142}," shard.mu.",[132,2263,2264],{"class":153},"RUnlock",[132,2266,1775],{"class":142},[132,2268,2270,2273,2275],{"class":134,"line":2269},27,[132,2271,2272],{"class":142},"    v, ok ",[132,2274,180],{"class":138},[132,2276,2277],{"class":142}," shard.data[key]\n",[132,2279,2281,2283],{"class":134,"line":2280},28,[132,2282,694],{"class":138},[132,2284,2285],{"class":142}," v, ok\n",[132,2287,2289],{"class":134,"line":2288},29,[132,2290,293],{"class":142},[26,2292,2294],{"id":2293},"почему-работает","Почему работает",[42,2296,2297],{},"При N шардах и равномерном хэше: вероятность что 2 горутины попадут в один шард ≈ 1\u002FN. Чем больше N — тем меньше contention.",[31,2299,2302],{"className":2300,"code":2301,"language":36},[34],"1 шард:   все горутины → 1 мьютекс    (максимальный contention)\n16 шардов: горутины → 16 мьютексов    (contention \u002F 16)\n",[38,2303,2301],{"__ignoreMap":40},[26,2305,2307],{"id":2306},"выбор-количества-шардов","Выбор количества шардов",[10,2309,2310,2313,2316,2319],{},[13,2311,2312],{},"Слишком мало → мало выигрыша",[13,2314,2315],{},"Слишком много → overhead на память (N мьютексов + N мап)",[13,2317,2318],{},"Типичное значение: количество ядер × 4..16",[13,2320,2321],{},"Бенчмаркай под свою нагрузку",[26,2323,2325],{"id":2324},"продвинутый-вариант-внешний-роутинг","Продвинутый вариант: внешний роутинг",[42,2327,2328],{},"Можно вынести выбор шарда в интерфейс — вызывающий код явно роутит горутины к нужным шардам, чтобы определённые горутины никогда не пересекались.",[26,2330,2332],{"id":2331},"где-используется","Где используется",[10,2334,2335,2338,2341,2344],{},[13,2336,2337],{},"sync.Map (внутри Go runtime)",[13,2339,2340],{},"Локальные очереди планировщика Go (P-локальные)",[13,2342,2343],{},"Concurrent hash maps в Java (ConcurrentHashMap)",[13,2345,2346],{},"Шардирование в базах данных — та же идея",[23,2348],{},[10,2350,2351,2354,2357],{},[13,2352,2353],{},"RCU (Read-Copy-Update) — атомарная подмена указателя на целую структуру данных. Читатели работают с копией, писатель создаёт новую и подменяет указатель",[13,2355,2356],{},"Работает когда операции — только чтение, а запись = полная замена (не модификация)",[13,2358,2359],{},"Нет мьютексов, нет блокировок: только atomic.Store\u002FLoad на указателе",[23,2361],{},[26,2363,116],{"id":2364},"идея-1",[42,2366,2367],{},"Есть кэш (мапа), который обновляется раз в минуту из внешнего хранилища. Между обновлениями — только чтение.",[42,2369,2370],{},[46,2371,2372],{},"С мьютексом:",[31,2374,2376],{"className":126,"code":2375,"language":128,"meta":40,"style":40},"type Cache struct {\n    mu   sync.Mutex\n    data map[string]string\n}\n\nfunc (c *Cache) Get(key string) string {\n    c.mu.Lock()\n    defer c.mu.Unlock()\n    return c.data[key]\n}\n",[38,2377,2378,2388,2398,2412,2416,2420,2450,2460,2472,2479],{"__ignoreMap":40},[132,2379,2380,2382,2384,2386],{"class":134,"line":135},[132,2381,526],{"class":138},[132,2383,1886],{"class":153},[132,2385,532],{"class":138},[132,2387,198],{"class":142},[132,2389,2390,2392,2394,2396],{"class":134,"line":174},[132,2391,1895],{"class":142},[132,2393,1696],{"class":153},[132,2395,553],{"class":142},[132,2397,1902],{"class":153},[132,2399,2400,2402,2404,2406,2408,2410],{"class":134,"line":192},[132,2401,1907],{"class":142},[132,2403,1910],{"class":138},[132,2405,1913],{"class":142},[132,2407,1916],{"class":138},[132,2409,1919],{"class":142},[132,2411,1335],{"class":138},[132,2413,2414],{"class":134,"line":201},[132,2415,293],{"class":142},[132,2417,2418],{"class":134,"line":223},[132,2419,568],{"emptyLinePlaceholder":567},[132,2421,2422,2424,2426,2429,2431,2434,2436,2438,2440,2442,2444,2446,2448],{"class":134,"line":239},[132,2423,139],{"class":138},[132,2425,143],{"class":142},[132,2427,2428],{"class":146},"c ",[132,2430,150],{"class":138},[132,2432,2433],{"class":153},"Cache",[132,2435,157],{"class":142},[132,2437,2204],{"class":153},[132,2439,162],{"class":142},[132,2441,2135],{"class":146},[132,2443,2138],{"class":138},[132,2445,157],{"class":142},[132,2447,1916],{"class":138},[132,2449,198],{"class":142},[132,2451,2452,2455,2458],{"class":134,"line":263},[132,2453,2454],{"class":142},"    c.mu.",[132,2456,2457],{"class":153},"Lock",[132,2459,1775],{"class":142},[132,2461,2462,2464,2467,2470],{"class":134,"line":272},[132,2463,2258],{"class":138},[132,2465,2466],{"class":142}," c.mu.",[132,2468,2469],{"class":153},"Unlock",[132,2471,1775],{"class":142},[132,2473,2474,2476],{"class":134,"line":278},[132,2475,694],{"class":138},[132,2477,2478],{"class":142}," c.data[key]\n",[132,2480,2481],{"class":134,"line":284},[132,2482,293],{"class":142},[42,2484,2485],{},[46,2486,2487],{},"С RCU (без мьютекса):",[31,2489,2491],{"className":126,"code":2490,"language":128,"meta":40,"style":40},"type Cache struct {\n    data unsafe.Pointer  \u002F\u002F *map[string]string\n}\n\nfunc (c *Cache) Get(key string) string {\n    m := (*map[string]string)(atomic.LoadPointer(&c.data))\n    return (*m)[key]\n}\n\nfunc (c *Cache) sync() {\n    newMap := loadFromStorage()           \u002F\u002F загрузка без блокировок\n    p := unsafe.Pointer(&newMap)\n    atomic.StorePointer(&c.data, p)       \u002F\u002F атомарная подмена\n}\n",[38,2492,2493,2503,2516,2520,2524,2552,2584,2595,2599,2603,2621,2637,2655,2673],{"__ignoreMap":40},[132,2494,2495,2497,2499,2501],{"class":134,"line":135},[132,2496,526],{"class":138},[132,2498,1886],{"class":153},[132,2500,532],{"class":138},[132,2502,198],{"class":142},[132,2504,2505,2507,2509,2511,2513],{"class":134,"line":174},[132,2506,1907],{"class":142},[132,2508,550],{"class":153},[132,2510,553],{"class":142},[132,2512,257],{"class":153},[132,2514,2515],{"class":235},"  \u002F\u002F *map[string]string\n",[132,2517,2518],{"class":134,"line":192},[132,2519,293],{"class":142},[132,2521,2522],{"class":134,"line":201},[132,2523,568],{"emptyLinePlaceholder":567},[132,2525,2526,2528,2530,2532,2534,2536,2538,2540,2542,2544,2546,2548,2550],{"class":134,"line":223},[132,2527,139],{"class":138},[132,2529,143],{"class":142},[132,2531,2428],{"class":146},[132,2533,150],{"class":138},[132,2535,2433],{"class":153},[132,2537,157],{"class":142},[132,2539,2204],{"class":153},[132,2541,162],{"class":142},[132,2543,2135],{"class":146},[132,2545,2138],{"class":138},[132,2547,157],{"class":142},[132,2549,1916],{"class":138},[132,2551,198],{"class":142},[132,2553,2554,2557,2559,2561,2564,2566,2568,2570,2572,2575,2577,2579,2581],{"class":134,"line":239},[132,2555,2556],{"class":142},"    m ",[132,2558,180],{"class":138},[132,2560,143],{"class":142},[132,2562,2563],{"class":138},"*map",[132,2565,1913],{"class":142},[132,2567,1916],{"class":138},[132,2569,1919],{"class":142},[132,2571,1916],{"class":138},[132,2573,2574],{"class":142},")(atomic.",[132,2576,212],{"class":153},[132,2578,162],{"class":142},[132,2580,217],{"class":138},[132,2582,2583],{"class":142},"c.data))\n",[132,2585,2586,2588,2590,2592],{"class":134,"line":263},[132,2587,694],{"class":138},[132,2589,143],{"class":142},[132,2591,150],{"class":138},[132,2593,2594],{"class":142},"m)[key]\n",[132,2596,2597],{"class":134,"line":272},[132,2598,293],{"class":142},[132,2600,2601],{"class":134,"line":278},[132,2602,568],{"emptyLinePlaceholder":567},[132,2604,2605,2607,2609,2611,2613,2615,2617,2619],{"class":134,"line":284},[132,2606,139],{"class":138},[132,2608,143],{"class":142},[132,2610,2428],{"class":146},[132,2612,150],{"class":138},[132,2614,2433],{"class":153},[132,2616,157],{"class":142},[132,2618,1696],{"class":153},[132,2620,1427],{"class":142},[132,2622,2623,2626,2628,2631,2634],{"class":134,"line":290},[132,2624,2625],{"class":142},"    newMap ",[132,2627,180],{"class":138},[132,2629,2630],{"class":153}," loadFromStorage",[132,2632,2633],{"class":142},"()           ",[132,2635,2636],{"class":235},"\u002F\u002F загрузка без блокировок\n",[132,2638,2639,2642,2644,2646,2648,2650,2652],{"class":134,"line":447},[132,2640,2641],{"class":142},"    p ",[132,2643,180],{"class":138},[132,2645,669],{"class":142},[132,2647,257],{"class":153},[132,2649,162],{"class":142},[132,2651,217],{"class":138},[132,2653,2654],{"class":142},"newMap)\n",[132,2656,2657,2660,2663,2665,2667,2670],{"class":134,"line":452},[132,2658,2659],{"class":142},"    atomic.",[132,2661,2662],{"class":153},"StorePointer",[132,2664,162],{"class":142},[132,2666,217],{"class":138},[132,2668,2669],{"class":142},"c.data, p)       ",[132,2671,2672],{"class":235},"\u002F\u002F атомарная подмена\n",[132,2674,2675],{"class":134,"line":661},[132,2676,293],{"class":142},[26,2678,2680],{"id":2679},"как-работает","Как работает",[31,2682,2685],{"className":2683,"code":2684,"language":36},[34],"Горутина A: Load → получила старую мапу → работает с ней\n                   ↓ (в это время)\nСинхронизатор: Store → подменил указатель на новую мапу\n                   ↓\nГорутина B: Load → получила НОВУЮ мапу → работает с ней\n",[38,2686,2684],{"__ignoreMap":40},[42,2688,2689,2690,2693],{},"A и B работают с ",[46,2691,2692],{},"разными"," копиями одновременно. Это нормально — данные обновляются раз в минуту, секундное расхождение некритично.",[42,2695,2696],{},"Когда A закончит — следующий Load вернёт уже новую мапу.",[26,2698,2700],{"id":2699},"когда-подходит","Когда подходит",[10,2702,2703,2706,2709],{},[13,2704,2705],{},"Кэш с периодическим обновлением (read-heavy, write-rare)",[13,2707,2708],{},"Конфигурация, загружаемая из внешнего источника",[13,2710,2711],{},"Любая структура где запись = полная замена, не модификация отдельных полей",[42,2713,2714,2717],{},[46,2715,2716],{},"Не подходит",": если нужно добавлять\u002Fудалять отдельные элементы конкурентно.",[23,2719],{},[10,2721,2722,2725,2728],{},[13,2723,2724],{},"4 уровня: грубая → тонкая → оптимистичная → ленивая синхронизация. Каждый увеличивает пропускную способность и сложность кода",[13,2726,2727],{},"Множество на отсортированном связанном списке как модельная структура. Метод find с двумя указателями (prev, curr)",[13,2729,2730],{},"Аналогия с БД: блокировка таблицы → блокировка строк → оптимистичные транзакции",[23,2732],{},[26,2734,2736],{"id":2735},"модельная-задача","Модельная задача",[42,2738,2739],{},"Множество (set) на отсортированном связанном списке. Операции: contains, add, remove. Инвариант: элементы по возрастанию, без дубликатов.",[31,2741,2744],{"className":2742,"code":2743,"language":36},[34],"head(0) → 10 → 20 → 30 → 40 → nil\n",[38,2745,2743],{"__ignoreMap":40},[42,2747,2748],{},"find(val) бежит двумя указателями (prev, curr) пока curr.value \u003C val.",[26,2750,2752],{"id":2751},"_1-грубая-синхронизация-5972-строки","1. Грубая синхронизация (59→72 строки)",[31,2754,2756],{"className":126,"code":2755,"language":128,"meta":40,"style":40},"type Set struct {\n    mu   sync.Mutex\n    head *Node\n}\n",[38,2757,2758,2769,2779,2788],{"__ignoreMap":40},[132,2759,2760,2762,2765,2767],{"class":134,"line":135},[132,2761,526],{"class":138},[132,2763,2764],{"class":153}," Set",[132,2766,532],{"class":138},[132,2768,198],{"class":142},[132,2770,2771,2773,2775,2777],{"class":134,"line":174},[132,2772,1895],{"class":142},[132,2774,1696],{"class":153},[132,2776,553],{"class":142},[132,2778,1902],{"class":153},[132,2780,2781,2783,2785],{"class":134,"line":192},[132,2782,584],{"class":142},[132,2784,150],{"class":138},[132,2786,2787],{"class":153},"Node\n",[132,2789,2790],{"class":134,"line":201},[132,2791,293],{"class":142},[42,2793,2794],{},"Один мьютекс на всё. Lock перед каждой операцией. Просто, но все горутины конкурируют за один мьютекс.",[26,2796,2798],{"id":2797},"_2-тонкая-синхронизация-7285-строк","2. Тонкая синхронизация (72→85 строк)",[42,2800,2801,2802,2805],{},"Мьютекс в ",[46,2803,2804],{},"каждом узле",". Итерация = hand-over-hand locking:",[31,2807,2810],{"className":2808,"code":2809,"language":36},[34],"Lock(prev) → Lock(curr) → Unlock(prev) → prev=curr\n→ Lock(curr.next) → Unlock(prev) → ...\n",[38,2811,2809],{"__ignoreMap":40},[42,2813,2814,2815,2818],{},"Две горутины могут ",[46,2816,2817],{},"одновременно"," двигаться по списку если они на расстоянии друг от друга.",[42,2820,2821,2824],{},[46,2822,2823],{},"Удаление",": захватить prev + curr (2 мьютекса). Иначе аномалии: разрыв списка, потеря элементов.",[42,2826,2827,2830],{},[46,2828,2829],{},"Добавление",": формально хватает 1 мьютекса, но на практике захватывают 2 (переиспользование find).",[26,2832,2834],{"id":2833},"_3-оптимистичная-синхронизация-85145-строк","3. Оптимистичная синхронизация (85→145 строк)",[42,2836,2837,2838,2841],{},"Итерация ",[46,2839,2840],{},"без мьютексов"," (только атомики). Мьютексы захватываются только для модификации.",[42,2843,2844],{},"Но между \"нашёл элемент\" и \"захватил мьютекс\" кто-то мог удалить\u002Fдобавить.",[42,2846,2847,2850],{},[46,2848,2849],{},"Решение — валидация",": после захвата мьютексов пробежаться сначала списка → проверить что элементы ещё существуют и связь между ними цела.",[31,2852,2854],{"className":126,"code":2853,"language":128,"meta":40,"style":40},"func (s *Set) Add(val int) bool {\n    for {\n        prev, curr := s.find(val)         \u002F\u002F без мьютексов\n        prev.Lock(); curr.Lock()           \u002F\u002F захватили\n        if s.validate(prev, curr) {        \u002F\u002F повторный проход сначала\n            \u002F\u002F элементы на месте → добавляем\n            break\n        }\n        prev.Unlock(); curr.Unlock()       \u002F\u002F не валидно → заново\n    }\n}\n",[38,2855,2856,2885,2891,2910,2927,2942,2947,2952,2956,2972,2976],{"__ignoreMap":40},[132,2857,2858,2860,2862,2864,2866,2869,2871,2873,2875,2877,2879,2881,2883],{"class":134,"line":135},[132,2859,139],{"class":138},[132,2861,143],{"class":142},[132,2863,147],{"class":146},[132,2865,150],{"class":138},[132,2867,2868],{"class":153},"Set",[132,2870,157],{"class":142},[132,2872,1745],{"class":153},[132,2874,162],{"class":142},[132,2876,165],{"class":146},[132,2878,168],{"class":138},[132,2880,157],{"class":142},[132,2882,2220],{"class":138},[132,2884,198],{"class":142},[132,2886,2887,2889],{"class":134,"line":174},[132,2888,195],{"class":138},[132,2890,198],{"class":142},[132,2892,2893,2896,2898,2901,2904,2907],{"class":134,"line":192},[132,2894,2895],{"class":142},"        prev, curr ",[132,2897,180],{"class":138},[132,2899,2900],{"class":142}," s.",[132,2902,2903],{"class":153},"find",[132,2905,2906],{"class":142},"(val)         ",[132,2908,2909],{"class":235},"\u002F\u002F без мьютексов\n",[132,2911,2912,2915,2917,2920,2922,2924],{"class":134,"line":201},[132,2913,2914],{"class":142},"        prev.",[132,2916,2457],{"class":153},[132,2918,2919],{"class":142},"(); curr.",[132,2921,2457],{"class":153},[132,2923,2633],{"class":142},[132,2925,2926],{"class":235},"\u002F\u002F захватили\n",[132,2928,2929,2931,2933,2936,2939],{"class":134,"line":223},[132,2930,242],{"class":138},[132,2932,2900],{"class":142},[132,2934,2935],{"class":153},"validate",[132,2937,2938],{"class":142},"(prev, curr) {        ",[132,2940,2941],{"class":235},"\u002F\u002F повторный проход сначала\n",[132,2943,2944],{"class":134,"line":239},[132,2945,2946],{"class":235},"            \u002F\u002F элементы на месте → добавляем\n",[132,2948,2949],{"class":134,"line":263},[132,2950,2951],{"class":138},"            break\n",[132,2953,2954],{"class":134,"line":272},[132,2955,275],{"class":142},[132,2957,2958,2960,2962,2964,2966,2969],{"class":134,"line":278},[132,2959,2914],{"class":142},[132,2961,2469],{"class":153},[132,2963,2919],{"class":142},[132,2965,2469],{"class":153},[132,2967,2968],{"class":142},"()       ",[132,2970,2971],{"class":235},"\u002F\u002F не валидно → заново\n",[132,2973,2974],{"class":134,"line":284},[132,2975,287],{"class":142},[132,2977,2978],{"class":134,"line":290},[132,2979,293],{"class":142},[26,2981,2983],{"id":2982},"_4-ленивая-синхронизация-145130-строк","4. Ленивая синхронизация (145→~130 строк)",[42,2985,2986,2987,2990],{},"Вместо повторного прохода — ",[46,2988,2989],{},"флаг deleted"," в каждом узле.",[42,2992,2993,2994],{},"Валидация: ",[38,2995,2996],{},"!prev.dropped && !curr.dropped && prev.next == curr",[31,2998,3000],{"className":126,"code":2999,"language":128,"meta":40,"style":40},"func (s *Set) validate(prev, curr *Node) bool {\n    return !prev.dropped && !curr.dropped &&\n           prev.getNext() == curr\n}\n",[38,3001,3002,3040,3061,3076],{"__ignoreMap":40},[132,3003,3004,3006,3008,3010,3012,3014,3016,3018,3020,3023,3025,3028,3031,3034,3036,3038],{"class":134,"line":135},[132,3005,139],{"class":138},[132,3007,143],{"class":142},[132,3009,147],{"class":146},[132,3011,150],{"class":138},[132,3013,2868],{"class":153},[132,3015,157],{"class":142},[132,3017,2935],{"class":153},[132,3019,162],{"class":142},[132,3021,3022],{"class":146},"prev",[132,3024,1410],{"class":142},[132,3026,3027],{"class":146},"curr",[132,3029,3030],{"class":138}," *",[132,3032,3033],{"class":153},"Node",[132,3035,157],{"class":142},[132,3037,2220],{"class":138},[132,3039,198],{"class":142},[132,3041,3042,3044,3047,3050,3053,3055,3058],{"class":134,"line":174},[132,3043,694],{"class":138},[132,3045,3046],{"class":138}," !",[132,3048,3049],{"class":142},"prev.dropped ",[132,3051,3052],{"class":138},"&&",[132,3054,3046],{"class":138},[132,3056,3057],{"class":142},"curr.dropped ",[132,3059,3060],{"class":138},"&&\n",[132,3062,3063,3066,3069,3071,3073],{"class":134,"line":192},[132,3064,3065],{"class":142},"           prev.",[132,3067,3068],{"class":153},"getNext",[132,3070,321],{"class":142},[132,3072,358],{"class":138},[132,3074,3075],{"class":142}," curr\n",[132,3077,3078],{"class":134,"line":201},[132,3079,293],{"class":142},[42,3081,3082,3085],{},[46,3083,3084],{},"Важно",": внутри мьютексов всё равно нужны атомики для указателей — горутины бегают по списку без мьютексов (через атомики), поэтому модификация указателей должна быть атомарной.",[42,3087,3088,3089,3092,3093,553],{},"Удаление: сначала ",[38,3090,3091],{},"curr.dropped = true",", потом ",[38,3094,3095],{},"prev.next = curr.next",[26,3097,3099],{"id":3098},"сводка","Сводка",[31,3101,3104],{"className":3102,"code":3103,"language":36},[34],"                  Грубая    Тонкая    Оптимист.  Ленивая\nСтрок кода        72        85        145        ~130\nМьютексов          1         N (в узлах) N         N\nИтерация          под mx    hand-over  атомики    атомики\nВалидация         —         —          повт.проход  флаг\nContention        макс      меньше     ещё меньше  мин\n",[38,3105,3103],{"__ignoreMap":40},[26,3107,3109],{"id":3108},"дальнейшие-оптимизации","Дальнейшие оптимизации",[1231,3111,3112,3115,3118],{},[13,3113,3114],{},"Полностью lock-free (только атомики, без мьютексов)",[13,3116,3117],{},"Шардирование (разрезать список на N частей)",[13,3119,3120],{},"Комбинация: lock-free + шардирование",[23,3122],{},[10,3124,3125,3128,3131],{},[13,3126,3127],{},"Без правильной синхронизации: потеря элементов, разрыв списка, распад на два списка",[13,3129,3130],{},"Удаление: захвати prev + curr. Иначе параллельное удаление\u002Fдобавление соседних элементов ломает связи",[13,3132,3133],{},"Принцип: для любой модификации связи A→B нужна блокировка обоих концов",[23,3135],{},[26,3137,3139],{"id":3138},"аномалия-1-потеря-добавленного-элемента","Аномалия 1: потеря добавленного элемента",[31,3141,3144],{"className":3142,"code":3143,"language":36},[34],"Начало:    10 → 20 → 30 → 40\n\nГорутина 1: add(25) — нашла позицию между 20 и 30, засыпает\nГорутина 2: add(27) — добавила между 20 и 30\n            10 → 20 → 27 → 30 → 40\n\nГорутина 1: просыпается, ставит 25.next = 30 (старый next 20)\n            20.next = 25\n            Результат: 10 → 20 → 25 → 30 → 40\n            Элемент 27 потерян!\n",[38,3145,3143],{"__ignoreMap":40},[42,3147,3148],{},"Проблема: горутина 1 использует устаревшую связь 20→30.",[26,3150,3152],{"id":3151},"аномалия-2-потеря-элемента-при-удалении","Аномалия 2: потеря элемента при удалении",[31,3154,3157],{"className":3155,"code":3156,"language":36},[34],"Начало:    10 → 20 → 30 → 40\n\nГорутина 1: remove(20) — засыпает перед обновлением связи\nГорутина 2: remove(30) — удаляет: 20.next = 40\n            10 → 20 → 40\n\nГорутина 1: просыпается, удаляет 20: 10.next = 30\n            Но 30 уже удалён!\n            10 → 30 → ???  (список сломан)\n",[38,3158,3156],{"__ignoreMap":40},[26,3160,3162],{"id":3161},"аномалия-3-распад-списка","Аномалия 3: распад списка",[31,3164,3167],{"className":3165,"code":3166,"language":36},[34],"Начало:    10 → 20 → 30 → 40\n\nГорутина 1: remove(30) — засыпает\nГорутина 2: remove(20) — удаляет: 10.next = 30\n            10 → 30 → 40\n\nГорутина 1: просыпается, удаляет 30: 20.next = 40\n            Но 20 уже не в списке!\n            Список распался на два:  10 → 30  и  20 → 40\n",[38,3168,3166],{"__ignoreMap":40},[26,3170,3172],{"id":3171},"решение-захват-prev-curr","Решение: захват prev + curr",[42,3174,3175],{},"Для удаления элемента 30: Lock(20) + Lock(30).",[31,3177,3180],{"className":3178,"code":3179,"language":36},[34],"  Lock(20)  Lock(30)\n     ↓         ↓\n→ [20] ----→ [30] → 40\n",[38,3181,3179],{"__ignoreMap":40},[42,3183,3184],{},"Теперь никто не может:",[10,3186,3187,3190,3193,3196],{},[13,3188,3189],{},"Удалить 20 (нужен Lock(20) — занят)",[13,3191,3192],{},"Удалить 30 (нужен Lock(30) — занят)",[13,3194,3195],{},"Удалить 40 (нужен Lock(30) как prev — занят)",[13,3197,3198],{},"Добавить между 20 и 30 (нужен Lock(20) или Lock(30) — заняты)",[42,3200,3201,3202,3205],{},"Элемент 10 ",[46,3203,3204],{},"можно"," удалить — его блокировка свободна, и это не влияет на операцию с 30.",[26,3207,3209],{"id":3208},"deadlock-prevention","Deadlock prevention",[42,3211,3212],{},"Hand-over-hand: всегда захватываем в порядке prev → curr (слева направо). Все горутины двигаются в одном направлении → циклических зависимостей нет → deadlock невозможен.",[23,3214],{},[3216,3217,3218],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}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 pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":40,"searchDepth":174,"depth":174,"links":3220},[3221,3222,3223,3224,3225,3226,3227,3228,3229,3230,3231,3232,3233,3234,3235,3236,3237,3238,3239,3240,3241,3242,3243,3244,3245,3246,3247,3248,3249,3250,3251,3252,3253,3254,3255,3256,3257,3258,3259,3260,3261,3262,3263],{"id":28,"depth":174,"text":29},{"id":56,"depth":174,"text":57},{"id":69,"depth":174,"text":70},{"id":115,"depth":174,"text":116},{"id":122,"depth":174,"text":123},{"id":296,"depth":174,"text":297},{"id":457,"depth":174,"text":458},{"id":468,"depth":174,"text":469},{"id":501,"depth":174,"text":502},{"id":515,"depth":174,"text":516},{"id":708,"depth":174,"text":123},{"id":978,"depth":174,"text":297},{"id":1225,"depth":174,"text":1226},{"id":1267,"depth":174,"text":1268},{"id":1483,"depth":174,"text":1484},{"id":1515,"depth":174,"text":1516},{"id":1531,"depth":174,"text":1532},{"id":1561,"depth":174,"text":1562},{"id":1589,"depth":174,"text":1590},{"id":1605,"depth":174,"text":1606},{"id":1615,"depth":174,"text":1616},{"id":1838,"depth":174,"text":1839},{"id":1873,"depth":174,"text":1874},{"id":1931,"depth":174,"text":1932},{"id":2293,"depth":174,"text":2294},{"id":2306,"depth":174,"text":2307},{"id":2324,"depth":174,"text":2325},{"id":2331,"depth":174,"text":2332},{"id":2364,"depth":174,"text":116},{"id":2679,"depth":174,"text":2680},{"id":2699,"depth":174,"text":2700},{"id":2735,"depth":174,"text":2736},{"id":2751,"depth":174,"text":2752},{"id":2797,"depth":174,"text":2798},{"id":2833,"depth":174,"text":2834},{"id":2982,"depth":174,"text":2983},{"id":3098,"depth":174,"text":3099},{"id":3108,"depth":174,"text":3109},{"id":3138,"depth":174,"text":3139},{"id":3151,"depth":174,"text":3152},{"id":3161,"depth":174,"text":3162},{"id":3171,"depth":174,"text":3172},{"id":3208,"depth":174,"text":3209},"advanced","md",{},"concurrency","generics-reflect","\u002F03-concurrency\u002F06-lock-free","concurrency-patterns",{"title":5,"description":40},"lock-free","03-concurrency\u002F06-lock-free\u002Findex",[3272,3275,3276,3277,3278,3279,3280],"CAS","atomic","стек Трайбера","очередь Майкла-Скотта","ABA","RCU","mnZE-cHhMJO1TvVhRhd_WUSTXYtFW-V782A4LlRufF4",1776289835680]