AI 的出现带来了更加不确定的变化。一方面来说,AI
会带来新的市场和新的创业机会,相关领域的从业人员可能因此暂时成为小资产阶级。但在另一方面,AI
正在威胁程序员的岗位。让 AI
参与编程工作,会极大提高开发效率,产业的劳动密集型的现状可能因此改变,就像流水线工厂代替旧的劳动密集型工坊。由于训练
AI 需要高性能 GPU,这可能是一般工人无法拥有的。因此在可预见的未来,高阶
AI
可能像流水线机器一样,成为资本集团才能拥有的生产资料。如果是这样的话,普通程序员绝无可能保住小资产阶级的地位。
总结一下。程序员本是类似于小手工业者的小资产阶级。但是随着产业的发展,独立开发者难以与企业竞争,只能被企业雇佣。但成为工人的程序员并非完全是无产阶级,因为市场开发之初的“创业机会主义”和由此带来的分红,很多程序员都有小资产阶级的性质。然而随着市场逐渐被垄断集团瓜分,程序员的小资产阶级性质会逐渐消失,最终成为无产阶级。创业成功、当上老板的程序员自然是资产阶级。被收购、套现离场的创业者的阶级性质取决于资产状况,但是他们的阶级地位并不稳固。AI
的出现带来新的变化:新的市场会带来新的机会;但高阶 AI
可能成为普通人无法拥有的生产资料,普通程序员可能因此沦为真正的无产阶级。
这样的配置让 CoreDNS 监听 192.168.1.2:58,在
hosts 中配置域名解析规则。其他域名默认 forward 到系统 DNS
解析。在路由器中将 NAS 的 IP 地址固定为
192.168.1.2,并将首选 DNS 服务器设置为
192.168.1.2。这样任何通过我家路由器上网的设备都可以通过域名访问
NAS 上的服务。
为了让连上 VPN 的设备也能用同样的域名访问 NAS,我们在 Tailscale
的控制台设置一个 Split DNS,地址设置为 NAS 的虚拟 IP;并指定后缀为
luyuhuang.tech 的域名请求 NAS 上的 DNS 服务器。
由于 NAS 的虚拟 IP 和物理局域网的 IP 不同,所以还要让 CoreDNS
监听虚拟 IP,解析结果也要返回虚拟 IP。
一直以来文字排版都是一项复杂的工作。计算机出现不久后,人们就尝试用计算机取代铅字处理排版工作。现在计算机上的排版工具有很多。Microsoft
Office Word
可能是使用最广泛的排版工具。它容易上手,功能丰富,能够满足绝大多数办公场景。缺点是文件格式私有,价格昂贵;面对一些复杂排版需求(如公式排版)力不从心。随着
web 技术的发展,HTML + CSS 也可以作为排版工具;Markdown 这种可以编译成
HTML 的简易标记语言广泛应用于网络文字排版(本站的文章都是用 Markdown
写的)。但是 web
技术主要服务于网页设计,缺失换行、分页算法,也难以应对复杂排版。由著名计算机科学家、图灵奖获得者
Donald E. Knuth 发明的 及其衍生品
因其强大的功能、美观的排版效果、优秀的换行分页算法、出色的公式排版能力、灵活的可定制性,一直以来是排版系统的黄金标准。但是由于年代久远,上手难度高、历史包袱众多。那么有没有什么功能强大、容易上手、开源免费的排版系统推荐一下呢?有的兄弟,有的。今天我要推荐的
Typst 就是这样一款优秀的排版工具。
它本质上是一个有很多命名参数和一个普通参数 body
的函数,将内容 body 转换成一篇简历并返回。而
with
实际上是函数对象的一个方法,它返回一个预应用了给定参数的新函数。例如函数
let f(a, b) = a + b,f.with(1) 就等价于
(b) => f(1, b)。那么这里 resume.with(...)
就得到一个各种命名参数设置好了的新函数。这里的 show
语句会将整个文档作为参数传入这个新函数,我们就能得到一篇简历了。
加上编译参数 -fsanitize=address 启用 ASan
后,运行便会触发 ASan 报错:
$ gcc -o test test.c -fsanitize=address -g $ ./test ================================================================= ==65982==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000014 at pc 0x5600b2a24202 bp 0x7ffda82b5c70 sp 0x7ffda82b5c60 WRITE of size 4 at 0x602000000014 thread T0 #0 0x5600b2a24201 in main (/home/luyuhuang/test+0x1201) #1 0x7f7eee24c082 in __libc_start_main ../csu/libc-start.c:308 #2 0x5600b2a240ed in _start (/home/luyuhuang/test+0x10ed)
0x602000000014 is located 0 bytes to the right of 4-byte region [0x602000000010,0x602000000014) allocated by thread T0 here: #0 0x7f7eee527808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144 #1 0x5600b2a241be in main (/home/luyuhuang/test+0x11be) #2 0x7f7eee24c082 in __libc_start_main ../csu/libc-start.c:308 ...
ASan 告诉我们程序触发了一个堆越界 (heap-buffer-overflow)
的报错,尝试在地址 0x602000000014 写入 4
字节数据。随后打印出出错位置的堆栈。第 10 行 ASan
还告诉我们越界的内存是在哪个位置被分配的,随后打印出分配位置的堆栈。
常用参数
ASan
的参数有编译参数和运行时参数两种。常用的编译参数有:
-fsanitize=address 启用 ASan
-fno-omit-frame-pointer 获得更好的堆栈信息
ASan 专属的参数,GCC 使用 --param FLAG=VAL 传入,LLVM
使用 -mllvm -FLAG=VAL 传入:
了解了 ASan 原理之后就能更好地理解 ASan 的报错信息。ASan
报错时会打印出报错位置的 shadow 内存情况:
SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/luyuhuang/test+0x1201) in main Shadow bytes around the buggy address: 0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c047fff8000: fa fa[04]fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
协程与线程(进程)[1]都是模拟多任务(routine)并发执行的软件实现。操作系统线程在一个
CPU
线程中模拟并发执行多个任务,而协程(coroutine)在一个操作系统线程中模拟并发执行多个任务。
因此协程也被称为“用户态线程”、“轻量级线程”。之所以说“模拟并发执行”,是因为任务实际并没有同时运行,而是通过来回切换实现的。
所有的协程共享同一个 CPU,也就共享同样的 16
个通用寄存器。如果我们要把 A 协程切换成 B 协程,就要把当前 16
个通用寄存器的值存在 A 协程的数据结构里;然后再从 B 协程的数据结构里取出
B
协程的寄存器的值,写回通用寄存器中。我们还要处理栈。不过栈与寄存器不同,x86-64
规定 %rsp
寄存器(也是通用寄存器之一)存的值便是栈顶的地址。不同的协程不必共享栈,它们可以分配各自的栈,上下文切换时将
%rsp 指向各自的栈顶即可。
voidfoo() { printf("here is the new coroutine\n"); co_ctx_swap(&new_co->ctx, &main_co->ctx); printf("new coroutine resumed\n"); co_ctx_swap(&new_co->ctx, &main_co->ctx); }
In 2023 I spent most of my time on work, learning and dating.
Compared with the last year, devoted less time on this blog and
community. It might be a pretext but to be honest, writing a blog post
always consumes a lot of my energy, especially when there was a lot of
overtime this year. Anyway, I should write more posts in the new year,
and I’ll try to write some non-technical posts (well, partially because
they’re easy to write (and read)).
Let’s talk a little about professional skills over here. In the past,
I focused most of my efforts on coding skill, rather than engineering
skills, or let’s say, the business skills. This is because I love the
hacker spirit, and am fascinated by intricate program structures and
algorithms. But your boss just need one who solve problems, them don’t
care about computer science. To solve a problem, you have to consider
many things other than the computer - namely, the people around you. And
that’s exactly what software engineering does - to use some engineering
methods to prevent or eliminate mistakes made by humans. So when I focus
on hacking, I must keep an open mind on other skills, especially those
methodologies for problem-solving.
Goals
Keep up learning English
Read Understanding the Linux
Kernel (I’m not sure if I can finish it)
Keep up daily LeetCode
exercises
Keep up writing blogs, basically one post a
month
Read some non-technical
books
Keep up exercises
Learning
I finished learning Structure and Interpretation of Computer
Programs (SICP), an amazing book. Its content comprised of
functional programming, layering of program and data structure, OOP,
infinite streams, the metacircular evaluator, lazy evaluation,
compilation principle, etc. I have to say, SICP opened the gate
of computer science for me. Before then, I don’t really comprehend the
essential of computer science.
sicp
LeetCode
I kept on doing LeetCode like the past few years. The grid looks not
bad.
leetcode
Now I’ve solved 1182 problems. Last year it’s 912, so I solved 270
problems in 2023.
Language
I kept learning English in 2023, as I did in the past few years.
After continuously learning vocabulary on the APP baicizhan for
2024 days, I found it might not be a very effective way to memorize new
words for me at the moment. Therefore, In November, I started learning
Merriam-Webster’s Vocabulary Builder. This book organizes words
by their roots, besides telling you how to use a word, its history, and
related knowledge.
webster
I also read another amazing vocabulary builder, Word Power Made
Easy. To me, this book like an introduction to the etymology and at
the same time teaches you how to memorize over 3,000 words and continue
building your vocabulary.
webster
In addition, I kept leaning Japanese on Duolingo as I did in
2022.
webster
Creating
I only wrote 6 posts on the blog.
I created a repo luyuhuang/nvim, but it’s
just for personal configuration, should not be considered as a
contribution. I submitted some pull requests and issues to the community
and some of have been accepted.
Something Happy
Hiking and seeing the skyline of the city at the summit is quite
happy, especially with the one you love.
webster
Finally
At the end of a year, I always feel time flies by quickly. But after
I wrote the annual review, I found that the time of a year is quite
long, because you can literally do many things in a year and grow quite
a bit in certain aspects (say, I found my English writing skill is much
better than the last year). So in the new year, keep learning and
growing, and I believe we’ll get a better result. Happy New Year to
everybody.
在服务器编程中,事务往往是非常重要的,它的一个很重要的作用就是保证一系列操作的完整性。例如服务器处理某个请求要先后执行
a, b 两个修改操作,它们都有可能失败;如果 a 成功了但 b
失败了,事务会负责回滚 a 的修改。试想如果 a 操作是扣除余额,b
操作是发货,如果发货失败,钱就得退回去。如果服务器使用了支持事务的数据库系统,如
MySQL,事情就很好办。否则的话,实现类似的逻辑会比较棘手,也很容易犯错。
local original_pcall = pcall functionpcall(f, ...) begin() local ok, res = original_pcall(f, ...) if ok then commit() else rollback() end return ok, res end
由于 pcall
可以嵌套,i.e. pcall(function() pcall(function() end) end),我们使用栈保存事务的上下文,在
begin 中压栈,commit 和 rollback
时弹出栈。因此栈顶就是当前事务的上下文。调用 set
执行修改操作,它会将数据的原始值保存在上下文中。
local stack = {}
localfunctionbegin() table.insert(stack, {}) end
functionset(tab, key, val) local top = stack[#stack] if top then table.insert(top, {tab, key, tab[key]}) end tab[key] = val end
Commit
时,当前事务的赋值操作全部生效,当前事务造成的副作用亦是上层事务的副作用,需要将当前事务记录的数据原始值移动到上层事务(如果有的话)的上下文中。回滚时,从后往前依次取出每次
set
操作的原始值,将数据设置成修改前的值,完成回滚操作。
localfunctioncommit() local top = table.remove(stack) localpi = stack[#stack] ifpithen for _, assign inipairs(top) do table.insert(pi, assign) end end end
localfunctionrollback() local top = table.remove(stack) for i = #top, 1, -1do local tab, key, val = table.unpack(top[i], 1, 3) tab[key] = val end end
使用的时候不能直接赋值,需要调用
set。当然也可以做成元表,不过我不是很喜欢这样。
val = {} functiontest() local old = val set(_G, 'val', 42) set(old, 1, 1) error() end
pcall(test) -- false nil next(val) -- nil
整个实现可以说是非常简单且行之有效,开销也并不大。代码是我随手写的,它还有优化空间:stack
中的旧数据存储可以使用更紧凑的数据结构;代码可以用 C
实现提高性能等。
functionlive_grep_opts(opts) local flags = tostring(vim.v.count) local additional_args = {} local prompt_title = 'Live Grep' if flags:find('1') then prompt_title = prompt_title .. ' [.*]' else table.insert(additional_args, '--fixed-strings') end if flags:find('2') then prompt_title = prompt_title .. ' [w]' table.insert(additional_args, '--word-regexp') end if flags:find('3') then prompt_title = prompt_title .. ' [Aa]' table.insert(additional_args, '--case-sensitive') end
opts = opts or {} opts.additional_args = function()return additional_args end opts.prompt_title = prompt_title return opts end
localfunctionhome() local head = (vim.api.nvim_get_current_line():find('[^%s]') or1) - 1 local cursor = vim.api.nvim_win_get_cursor(0) cursor[2] = cursor[2] == head and0or head vim.api.nvim_win_set_cursor(0, cursor) end
vim.api.nvim_create_autocmd('BufReadPost', {callback = function() local line = vim.fn.line('\'"') if line > 1and line <= vim.fn.line('$') then vim.cmd.normal('g\'"') end end})
2022 is a bit tough for many of us. The pandemic disturbed manypeople’s life. The Chinese internet industry is not that prosperous inthe year: many companies layoffs and many people are unemployed, manygame projects were aborted due to suspended issued publishing licenses,many companies’ share prices fell, and so on. I was infected withCOVID-19 at the end of 2022 and suffered from fever, body aches, andfatigue for a week.
Anyway, It’s worth celebrating at the beginning of 2023 as I’m stillalive, I still have a job, and I’ve done some meaningful work in theyear. We still hope that the world will be better and that all wishescome true.
Goals
I finished some of the new year’s resolutions for 2022.
Keep up learning English.
Finish reading C++Primer
Read Understand the LinuxKernel(4 chapters)
Keep up daily LeetCodeexercises
Keep up writing blogs, at least one post amonth (only 9 posts)
Keep up exercises (running, push-up)(did not run, just push-ups every day)
Learning
On weekends, I usually spend time reading tech books.
Finished reading C++ Primer. Last year I read half ofit.
Read 4 chapters of Understand the Linux Kernel.
LeetCode
This year I spent lots of time doing LeetCode, basically everyday.
leetcode
Now I’ve done 912 exercises, I think it might be a small achievementthat’s not very easy to achieve. Doing LeetCode has somewhat improved mycoding skill and logical mind. it’s a good habit that’s worth keepingup.
leetcode
Language
English learning is not easy. I’ve spent plenty of time learningEnglish, including memorizing words and practicing listening andspeaking. I insist on memorizing words every day this year and untilnow, I’ve used Baicizhan to insist on memorizing words for 1709days.
English
I also spent lots of time on Open Language to practice listening andspeaking. It helped a lot at the beginning, but now I feel it doesn’thelp much. I think I’ve hit the English learning plateau.
In addition to English learning, I’ve been learning Japanese onDuolingo. I didn’t spend much time on that, basically two units a day.It’s just for fun.
duolingo
Creating
(only) Wrote 9 post on my blog.
This year I didn’t create much work in my spare time. I intended touse C++ to write a VPN software, but I only completed the most basicfeature and have not released an initial version. I just had somesporadic commit records on Github.
github
Something Happy
The happiest thing is that I’ve had a good time with the people Ilove. Thanks for her company to make me no longer feel lonely.
flowers
Finally
Not long ago I revisited My Life as McDull (麦兜故事), afamous Hongkong film I watched when I was a kid. But I didn’t understandits profound central idea at that age. I love what it said at theending:
Roasted chicken is easy to cook. The material is a chicken; themethod is to take a chicken and roast it, and then it’s done. If youwant it to be delicious, the secret is to cook it better.
mcdull
Well, life might be a simple process from birth to death. It’ssimple, but it also can be complicated if you want. If you want it to bebeautiful, the secret is to be positive and try to make it better.
Although we might be going to face challenges in 2023, we stillbelieve that good things are about to happen. Happy New Year to us.
Dependency-ordered before 可以 “后接” 一个 carries dependency的关系以延伸它的范围: 如果 a “dependency-ordered before” k 且 k “carriesa dependency into” b, 则 a “dependency-ordered before” b.Dependency-ordered before 可以直接构成 inter-thread happens-before的关系: 如果 a “dependency-ordered before” b 则 a “inter-threadhappens-before” b.
关于内存顺序, C++ 还有一些本文没提到的功能, 如内存栅栏 (memoryfences). C++ 的原子变量提供的操作也很多, 本文只提到了其中一部分.对其感兴趣或需要了解的同学可以参考 C++ Concurrency in Action 和cppreference.com. 限于篇幅, 本文的有些内容可能解释地不够详尽,或者定义不够严谨. 如果想了解概念的严格定义, 可以参考 cppreference.com;如果需要详细的讲解, 可以参考 C++ Concurrency in Action.
参考资料:
C++ Concurrency in Action: Practical Multithreading, AnthonyWilliams.
classC { public: C(const C &c) : p(c.p) { } // or C(const C &c) : p(newT(*c.p)) {}
C &operator=(const C &c) { if (&c == this) return *this; p = c.p; return *this; } // or C &operator=(const C &c) { if (&c == this) return *this; delete p; p = newT(*c.p); return *this; }
private: T *p; };
C++ 允许开发者控制对象拷贝时的行为. 我们可以仅拷贝指针,让拷贝前后指向同一个对象; 也可以拷贝指针指向的值,向用户隐藏这个类存在引用成员这一事实.