Linux学习记录--多线程共享变量
多线程编程中的共享变量:从协作到冲突,再到秩序
一组并发线程运行在一个进程的上下文中,每个线程都有自己独立的线程上下文,包括线程ID、栈、栈指针、程序计数器、条件码和通用目的寄存器值。每个线程和其他线程一起共享进程上下文剩余部分。这包括整个用户虚拟地址空间,它是由只读文本、读/写数据、堆以及所有的共享库代码和数据区域组成的。线程也共享相同的打开文件的集合。
一、共享变量:线程之间的“公共黑板”
在多线程程序中,共享变量是最直接的通信方式。它就像一块公共黑板,每个线程都可以读、可以写,用来传递信息、协调工作。
我们来看一个简单但典型的例子。
二、案例1:sharing.c
——共享变量让线程“各取所需”
2.1 程序代码(简化)
char **ptr; // 全局共享数组void* thread(void* vargp) {int myid = (int)(intptr_t)vargp;static int cnt = 0;printf("[%d]: %s (cnt=%d)\n", myid, ptr[myid], ++cnt);return NULL;
}int main() {pthread_t tid;char *messages[2] = {"Hello, I am Thread 1.","Hello, I am Thread 2."};ptr = messages; // 共享指针for (int i = 0; i < 2; i++)pthread_create(&tid, NULL, thread, (void*)(intptr_t)i);pthread_exit(NULL); // 主线程等待所有子线程结束
}
2.2 运行结果(某次)
2.3 共享变量如何“协作”?
ptr
是全局指针,两个线程都能访问;- 每个线程根据
myid
读取自己的那一行字符串; cnt
是函数内静态变量,每个线程独立一份(不是共享的),用来记录自己被调用的次数。
✅ 这里没有冲突:线程只“读”共享数据,不“写”共享数据。
2.4 技术小亮点:整数也能“伪装”成指针
pthread_create(&tid, NULL, thread, (void*)(intptr_t)i);
pthread_create
只能传void*
指针;- 但我们想传整数
i
,怎么办? - 用
(intptr_t)i
把整数“塞”进指针,避免了 malloc,简洁又安全。
小技巧:只传“值”,不传“地址”,避免访问已销毁的局部变量。
三、案例2:badcnt.c
——共享变量“看似没问题”,却悄悄出错
多线程程序就像一间开放式厨房,多个厨师可以共享调料架(共享变量),一起做出更高效的晚餐。但共享并不意味着“随便用”,一旦有人同时往同一瓶盐里加盐,就可能出问题。
3.1 程序逻辑:两个线程一起数数
volatile long cnt = 0; // 全局计数器void* thread(void* vargp) {long niters = *((long*)vargp);for (long i = 0; i < niters; i++)cnt++; // 看似简单的加法return NULL;
}int main(int argc, char** argv) {long niters = atoi(argv[1]);pthread_t tid1, tid2;pthread_create(&tid1, NULL, thread, &niters);pthread_create(&tid2, NULL, thread, &niters);pthread_join(tid1, NULL);pthread_join(tid2, NULL);printf("Final cnt = %ld (expected %ld)\n", cnt, 2 * niters);return 0;
}
3.2 运行结果:不稳定!
3.3 问题出在哪?cnt++
不是“一条指令”
cnt++
在汇编层面是三步:
- 读
cnt
到寄存器; - 寄存器加 1;
- 写回内存。
两个线程可能同时读到同一个值,然后各自加 1,写回同一个结果。于是,有一次加法“丢失”了。
时间 | 线程1 | 线程2 | 内存中 cnt |
---|---|---|---|
t1 | 读 cnt=10 | 10 | |
t2 | 读 cnt=10 | 10 | |
t3 | 寄存器=11 | 10 | |
t4 | 寄存器=11 | 10 | |
t5 | 写回 11 | 11 | |
t6 | 写回 11 | 11 |
期望是 12,结果是 11。
3.4 小结:共享变量“读写”才危险
sharing.c
:只读共享,安全;badcnt.c
:读写共享,出错;- 问题不在于“共享”,而在于同时写。
四、从冲突到秩序:我们该怎么办?
我们已经看到:
- 共享变量可以让线程高效协作;
- 但一旦涉及同时写,就可能悄悄出错;
- 错误不是“崩溃”,而是结果不对,更难发现。
那么,如何保证“写”操作不会互相打扰?
就像厨房里的盐罐,一次只能一个人用,我们需要一个“规则”。
这个规则,就是同步机制。
在下一篇文章中,我们将介绍:
- 信号量(semaphore) 和 互斥锁(mutex);
- 如何用信号量修复
badcnt.c
; - 如何把这种“规则”应用到更复杂的并发场景。
五、结语:共享变量不是“不能用”,而是“要会用”
场景 | 是否安全 | 原因 |
---|---|---|
线程只读共享变量 | ✅ 安全 | 不修改数据,互不干扰 |
线程写共享变量 | ⚠️ 需同步 | 可能“踩”到彼此的操作 |
共享变量就像公共厨房里的调料架:
- 每个人都可以看;
- 但同时往同一瓶里加盐,就要排队;
- 信号量,就是那张“一次只能一个人”的小纸条。
六、预告:下一篇《用信号量建立秩序:让多线程不再踩脚》
我们将:
- 用信号量修复
badcnt.c
; - 介绍二元信号量(互斥锁)和计数信号量;
- 带你一步步把“混乱”变成“秩序”。
如果本文对你有帮助,记得点赞 + 收藏 + 关注,第一时间获取下一篇更新!
欢迎留言交流你在多线程里踩过的坑~