欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > 【C语言】共享内存

【C语言】共享内存

2025/5/26 5:26:09 来源:https://blog.csdn.net/weixin_42127042/article/details/148202815  浏览:    关键词:【C语言】共享内存

在操作系统中,多个进程无法直接通过全局变量共享数据,因为每个进程拥有独立的虚拟地址空间。要实现进程间共享数据,需依赖操作系统提供的**共享内存(Shared Memory)**机制。以下是原理和实现步骤的详细解释:


一、为什么不能直接使用全局变量?

  1. 虚拟地址空间隔离
    每个进程的全局变量存储在自己的内存空间中,不同进程的相同虚拟地址可能指向不同的物理内存。

  2. 指针的局限性
    即使定义全局变量指针,指针的值(内存地址)在其他进程的上下文中无意义,直接访问会导致段错误(Segmentation Fault)。


二、共享内存的核心原理

通过操作系统分配一块物理内存,映射到多个进程的虚拟地址空间中,使它们能访问同一块内存区域。

关键步骤:
  1. 创建/获取共享内存块
    使用 shmget(System V)或 shm_open(POSIX)创建共享内存对象。

    // System V 示例
    int shm_id = shmget(IPC_PRIVATE, sizeof(int), IPC_CREAT | 0666);
    
  2. 映射到进程地址空间
    使用 shmat(System V)或 mmap(POSIX)将共享内存映射到进程的虚拟地址空间。

    int *shared_var = (int*)shmat(shm_id, NULL, 0);
    
  3. 访问共享数据
    通过映射后的指针操作共享内存中的数据。

    *shared_var = 42;  // 进程A写入数据
    
  4. 解除映射与清理
    使用 shmdt 解除映射,shmctlshm_unlink 删除共享内存。

    shmdt(shared_var);
    shmctl(shm_id, IPC_RMID, NULL);
    

三、共享内存的注意事项

  1. 内存地址的独立性
    不同进程中映射的虚拟地址可能不同,但指向同一物理内存。例如:

    • 进程A映射后地址为 0x7f123456
    • 进程B映射后地址为 0x7f654321
    • 二者实际操作的物理内存相同。
  2. 同步问题
    共享内存无内置同步机制,需配合互斥锁(Mutex)、信号量(Semaphore)等避免竞态条件。

    // 使用 POSIX 信号量同步
    sem_t *sem = sem_open("/my_sem", O_CREAT, 0666, 1);
    sem_wait(sem);
    *shared_var += 1;  // 原子操作
    sem_post(sem);
    
  3. 持久性与作用域

    • System V:共享内存由内核管理,显式删除前一直存在。
    • POSIX:可指定为持久化(文件系统关联)或匿名。

四、代码示例(POSIX 共享内存)

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>int main() {const char *name = "/my_shm";const int SIZE = sizeof(int);// 创建共享内存对象int shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);ftruncate(shm_fd, SIZE);// 映射到进程地址空间int *shared_var = (int*)mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);*shared_var = 0;  // 初始化if (fork() == 0) {// 子进程*shared_var = 42;printf("Child wrote: %d\n", *shared_var);} else {// 父进程sleep(1);  // 等待子进程写入printf("Parent read: %d\n", *shared_var);shm_unlink(name);  // 清理}return 0;
}

五、替代方案

  1. 内存映射文件(Memory-Mapped Files)
    将文件映射到内存,实现进程间共享,适合持久化数据。

  2. 消息队列(Message Queue)
    通过发送消息传递数据,避免直接共享内存。

  3. 分布式共享内存(DSM)
    在分布式系统中模拟共享内存(如 MPI)。


总结

  • 不要直接使用全局变量指针,必须通过共享内存机制。
  • 共享内存是高效的 IPC 方式,但需手动处理同步。
  • 实际开发中应优先考虑线程(共享地址空间)或更高级抽象(如 multiprocessing 库)。

在不同线程之间共享数据(如全局变量)时,线程的特性与进程有本质区别。由于线程属于同一个进程,线程之间天然共享进程的虚拟地址空间,因此全局变量可以直接被所有线程访问。以下是关键原理和注意事项的详细解释:


一、线程间共享全局变量的原理

  1. 内存模型差异

    • 进程:每个进程有独立的虚拟地址空间,全局变量属于进程私有。
    • 线程:所有线程共享同一进程的虚拟地址空间,全局变量对所有线程可见。
  2. 全局变量的共享性
    线程可以直接读写全局变量,无需额外操作。例如:

    #include <pthread.h>
    #include <stdio.h>int global_var = 0;  // 全局变量,所有线程共享void* thread_func(void* arg) {global_var = 42;  // 线程直接修改全局变量return NULL;
    }int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);pthread_join(tid, NULL);printf("Global value: %d\n", global_var);  // 输出 42return 0;
    }
    

二、线程间共享数据的注意事项

虽然线程可以直接共享全局变量,但必须解决以下问题:

1. 竞态条件(Race Condition)
  • 问题:多个线程同时修改同一变量时,结果取决于线程执行的顺序,导致不确定性。
  • 示例:两个线程同时执行 global_var++
    线程A读取 global_var=0 → 线程B读取 global_var=0 →  
    线程A写入 1 → 线程B写入 1(实际应为 2)
    
  • 解决方案:使用互斥锁(Mutex)、原子操作或信号量。
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* thread_func(void* arg) {pthread_mutex_lock(&mutex);global_var++;  // 原子性操作pthread_mutex_unlock(&mutex);return NULL;
    }
    
2. 内存可见性(Memory Visibility)
  • 问题:由于 CPU 缓存和编译器优化,一个线程修改的变量可能不会立即对其他线程可见。
  • 解决方案:使用内存屏障或 volatile 关键字(但 volatile 不保证原子性)。
    volatile int global_var = 0;  // 强制从内存读取而非缓存
    
3. 线程局部存储(Thread-Local Storage)
  • 场景:若某个变量需要在线程间独立(如线程私有计数器),需使用 TLS。
  • 实现:通过 thread_local(C11)或 __thread(GCC扩展)。
    thread_local int thread_local_var = 0;  // 每个线程有独立副本
    

三、指针在线程间的有效性

  • 直接访问:线程共享地址空间,全局变量的指针可以直接传递和使用。
    int* ptr = &global_var;
    // 线程A和线程B均可通过 ptr 操作同一内存
    
  • 动态内存:通过 malloc 分配的堆内存,指针对所有线程有效。
    int* heap_var = malloc(sizeof(int));
    *heap_var = 42;  // 所有线程共享
    

四、代码示例(带同步的线程共享)

#include <pthread.h>
#include <stdio.h>int global_counter = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* increment(void* arg) {for (int i = 0; i < 100000; i++) {pthread_mutex_lock(&mutex);global_counter++;  // 受互斥锁保护pthread_mutex_unlock(&mutex);}return NULL;
}int main() {pthread_t t1, t2;pthread_create(&t1, NULL, increment, NULL);pthread_create(&t2, NULL, increment, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);printf("Final counter: %d\n", global_counter);  // 正确输出 200000return 0;
}
未同步的错误示例:
// 若移除锁,结果可能小于 200000(竞态条件)
void* increment(void* arg) {for (int i = 0; i < 100000; i++) {global_counter++;  // 非原子操作}return NULL;
}

五、线程间通信的其他方式

  1. 条件变量(Condition Variables)
    用于线程间通知状态变化(需配合互斥锁)。

    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
  2. 原子操作
    使用 C11 原子类型(atomic_int)避免锁开销。

    #include <stdatomic.h>
    atomic_int atomic_counter = 0;
    atomic_fetch_add(&atomic_counter, 1);  // 原子递增
    

总结

  • 线程共享全局变量:无需特殊操作,直接读写即可。
  • 必须同步:使用互斥锁、原子操作或条件变量避免竞态条件。
  • 指针有效:线程间传递的指针指向共享地址空间,可直接使用。
  • 性能与安全平衡:锁会引入开销,高并发场景优先考虑无锁设计(如原子操作或 CAS)。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词