网页计数器值低于访问次数问题及模拟程序解析
网页计数器数值低于实际访问次数的原因分析及C语言模拟程序解读
一、为什么计数器会“漏记”访问次数?
最核心的原因就是竞态条件(Race Condition)——当多个并发线程(或进程)同时操作共享的计数器变量时,因为修改操作不是原子性的,就会导致数据丢失。具体来说:
- 非原子的读写操作:对计数器的更新是“读取当前值→加1→写回新值”三个独立步骤,不是一次性完成的原子操作。如果两个线程同时读取到同一个旧值,各自加1后写回,结果就相当于只统计了一次访问,白白丢了一次计数。
- 缺少同步保护:没有用互斥锁、信号量这类同步机制来锁定计数器的访问,多个线程可以随意穿插修改操作,完全没有秩序。
- 多CPU缓存的影响:在多核CPU环境下,每个核心可能会把计数器缓存到自己的本地缓存里,线程读取的是缓存中的旧值,写入后其他核心的缓存没有及时同步,也会导致后续线程读到错误的数值。
二、解读这段C语言模拟程序
先把完整的程序补全(你给出的代码有截断,我补全了核心逻辑):
#include <stdio.h> #include <unistd.h> #include <pthread.h> // 重复100次以模拟100次随机页面访问 #define RPT 100 // 网页访问计数器 int cnt=0; void* counter() { int cntLocalCopy; float r; cntLocalCopy = cnt; // 模拟服务器向浏览器提供页面的耗时工作 for(int i=0; i<10000; i++) { r = i * 1.23; // 用无意义的计算模拟处理请求的耗时 } cnt = cntLocalCopy + 1; return NULL; } int main() { pthread_t threads[RPT]; // 创建100个线程,每个线程模拟一次页面访问 for(int i=0; i<RPT; i++) { pthread_create(&threads[i], NULL, counter, NULL); } // 等待所有线程执行完毕 for(int i=0; i<RPT; i++) { pthread_join(threads[i], NULL); } printf("最终计数器值:%d\n", cnt); return 0; }
这段程序精准模拟了网页计数器漏记的场景,关键点如下:
- 全局共享变量无保护:
cnt是全局变量,所有100个线程都能直接读写,没有任何锁来限制访问顺序。 - 线程的执行逻辑放大了竞态条件:每个线程先把
cnt的值读到本地变量cntLocalCopy,然后去执行一段耗时的模拟工作(那段循环)。这段耗时操作故意拉长了“读取旧值”和“写回新值”之间的时间窗口,让多个线程更容易同时处于这个窗口中——比如线程A刚读完cnt=10就去忙计算了,这时候线程B、C也跟着读到cnt=10,等它们都忙完后,都会把11写回cnt,结果三次访问只记了一次。 - 最终结果必然小于100:运行这个程序,你会发现输出的计数器值大概率远小于100,这就是竞态条件导致的计数丢失,和实际网页服务器中遇到的问题完全一致。
如果要修复这个问题,只需要给计数器的访问加个互斥锁:
// 新增互斥锁 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void* counter() { int cntLocalCopy; float r; // 加锁,确保同一时间只有一个线程能访问cnt pthread_mutex_lock(&mutex); cntLocalCopy = cnt; cnt = cntLocalCopy + 1; pthread_mutex_unlock(&mutex); // 模拟工作放在锁外面,避免影响并发性能 for(int i=0; i<10000; i++) { r = i * 1.23; } return NULL; }
这样就能保证“读取-修改-写入”是原子操作,最终计数器值会准确等于100。
内容的提问来源于stack exchange,提问作者Ibo Cetinkaya
相关产品推荐
相关产品推荐

