系统编程-同步互斥机制

1|0同步互斥机制

  1. 同步概念
    所谓同步,即同时起步,协调一致。不同对象,对于“同步”的理解方式不一样(略有不同)。如设备同步,是指在两个设备之间规定一个共同的时间作为参考;数据同步,是指让两个或多个数据库内容保持一致,或者按需要部分保持一致;文件同步,是指两个或者多个文件夹的文件保持一致等等
    但是在编程中,通信中说的同步与生活中大家印象中同步是不一样的。“同”,协同,协助,互相配合,主旨在协同步调。按照预定先后次序运行。

  2. 线程同步,指一个线程发出来某一个调用时,在没有得到结果之前,该调用不返回。同时其它线程为了保证数据的一致性,不能调用该功能。说白了就是线程在执行任务时有先后顺序,为了防止线程资源被抢占。“同步”的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间,信号间等等都需要信号同步机制。

  3. 处理同步互斥方式有哪些

  • 信号量---------->进程
  • 有名信号量---------->进程
  • 无名信号量---------->线程
  • 互斥锁---------->线程
  • 读写锁---------->线程

2|0同步互斥方式之有名信号量

  1. 有名信号量的特点
    有名信号量与信号量非常相似,但是信号量的值只能是0/1,而有名信号量可以是0~+∞
    信号量使用空间+数据来处理互斥,而有名信号量只使用数据来处理
  2. 有名信号量的函数接口
  • 创建并打开一个有名信号量------->sem_open()
#include <fcntl.h> /* For O_* constants */ #include <sys/stat.h> /* For mode constants */ #include <semaphore.h> // 函数作用:初始化并且打开一个有名信号量 sem_t *sem_open( const char *name, // 参数1:有名信号量的名字,要求必须以“/”开头 int oflag // 参数2:O_CREAT----->如果不存在就创建 O_CREAT|O_EXCL----->不存在就创建,存在就报错 ); sem_t *sem_open(const char *name, int oflag, mode_t mode, // 参数3:八进制权限,例如0777 unsigned int value // 参数4:有名信号的起始值 ); // 成功返回:有名信号量的地址,失败返回:SEM_FAILED----->NULL/(sem_t*)-1 Link with -pthread.

注意:如果oflag中有O_CREAT这个选项,则这个mode与value必须要填

  1. 有名信号量的P操作
  • P操作:资源数-1操作------->sem_wait()
#include <semaphore.h> int sem_wait(sem_t *sem); //参数:有名信号量的地址 // 返回值:成功0------>资源-1,失败-1
  1. 有名信号量的V操作
  • V操作:资源数+1操作------->sem_post()
#include <semaphore.h> int sem_post(sem_t *sem); //参数:有名信号量的地址 // 返回值:成功0------>资源+1,失败-1 Link with -pthread.
  1. 关闭有名信号量--------sem_close()
#include <semaphore.h> int sem_close(sem_t *sem);//参数:有名信号量的地址 // 返回值:成功0,失败-1 Link with -pthread.
  1. 删除有名信号量--------sem_unlink()
#include <semaphore.h> int sem_unlink(const char *name); //参数:有名信号量的名字 // 返回值:成功0,失败-1 Link with -pthread.

实操(有名信号量+共享内存):
写端

#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <semaphore.h> #include <sys/types.h> #include <unistd.h> #include <sys/stat.h> #include <signal.h> #include <sys/shm.h> #include <fcntl.h> #include <string.h> #define SEM_NAME "/semname1" int main(int argc, char const *argv[]) { // 1.获取key键值 key_t key = ftok(".", 10); // 2.根据key值,获取共享内存的ID号 int shm_id = shmget(key, 1024, IPC_CREAT|0666); // 3.根据ID号将共享内存映射到进程的虚拟内存空间内 char* shm_p = shmat(shm_id, NULL, 0); if((void*)-1 == shm_p) { perror("shamat"); return -1; } sem_t* sem_p = sem_open(SEM_NAME, O_CREAT, 0777, 0); if(SEM_FAILED == sem_p) { perror("sem_open"); return -2; } while(1) { // 从键盘获取数据,存储到共享内存中 scanf("%s", shm_p); // 有名信号量的V操作 sem_post(sem_p); // 退出操作 if(!strncmp(shm_p, "exit", 4)) break; } // 解除映射 shmdt(shm_p); // 删除映射空间 shmctl(shm_id, IPC_RMID, NULL); // 关闭有名信号量 sem_close(sem_p); // 删除有名信号量 sem_unlink(SEM_NAME); return 0; }

读端

#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <semaphore.h> #include <sys/types.h> #include <unistd.h> #include <sys/stat.h> #include <signal.h> #include <sys/shm.h> #include <fcntl.h> #include <string.h> #define SEM_NAME "/semname1" int main(int argc, char const *argv[]) { // 1.获取key键值 key_t key = ftok(".", 10); // 2.根据key值,获取共享内存的ID号 int shm_id = shmget(key, 1024, IPC_CREAT|0666); // 3.根据ID号将共享内存映射到进程的虚拟内存空间内 char* shm_p = shmat(shm_id, NULL, 0); if((void*)-1 == shm_p) { perror("shamat"); return -1; } sem_t* sem_p = sem_open(SEM_NAME, O_CREAT, 0777, 0); if(SEM_FAILED == sem_p) { perror("sem_open"); return -2; } while(1) { // 有名信号量的P操作 sem_wait(sem_p); printf("rev: %s\n", shm_p); // 退出操作 if(!strncmp(shm_p, "exit", 4)) break; } // 解除映射 shmdt(shm_p); // 删除映射空间 shmctl(shm_id, IPC_RMID, NULL); // 关闭有名信号量 sem_close(sem_p); // 删除有名信号量 sem_unlink(SEM_NAME); return 0; }

3|0同步互斥方式之无名信号量

  1. 什么是无名信号量
    一般作用于线程之间的互斥,由于是无名信号量,所以是没有名字的,不能用sem_open()打开
  2. 无名信号量的函数接口
  • 定义一个无名信号量
    sem_t sem-------无名信号不是一个文件,是一个变量
  • 初始化一个无名信号量------>sem_init()
#include <semaphore.h> int sem_init( sem_t *sem, // 参数1:无名信号量的地址 int pshared, // 参数2: 0----->作用于线程之间,非0------>作用于进程之间 unsigned int value // 参数3:无名信号量的起始值 ); // 返回值:成功0,失败-1 Link with -pthread.

3.无名信号量的操作

  • P操作:资源数-1操作------->sem_wait()
#include <semaphore.h> int sem_wait(sem_t *sem); //参数:无名信号量的地址 // 返回值:成功0------>资源-1,失败-1
  1. 无名信号量的V操作
  • V操作:资源数+1操作------->sem_post()
#include <semaphore.h> int sem_post(sem_t *sem); //参数:无名信号量的地址 // 返回值:成功0------>资源+1,失败-1 Link with -pthread.
  1. 销毁无名信号量--------sem_destroy()
#include <semaphore.h> int sem_destroy(sem_t *sem); //参数:无名信号量的地址 Link with -pthread.

练习1:有一个进程,创建5个线程出来,每一个线程任务都是一样的
任务:将"helloworld"每隔1s打印一个字符。-->10s
练习2:有一个进程,创建5个线程出来,每一个线程任务都是一样的(使用无名信号量去处理同步互斥)
任务:将"helloworld"每隔1s打印一个字符。-->10s

  • 练习1
#include <stdio.h> #include <pthread.h> #include <unistd.h> void* func(void* arg) { char* p = "helloworld"; while(*p) { printf("%c", *p); ++p; fflush(stdout); sleep(1); } pthread_exit(NULL); } int main(int argc, char const *argv[]) { pthread_t thread[5]; for (int i = 0; i < 5; ++i) { pthread_create(&thread[i], NULL, func, NULL); } for (int i = 0; i < 5; ++i) { pthread_join(thread[i], NULL); } puts(""); return 0; }
  • 练习2
#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <semaphore.h> sem_t sem; void* func(void* arg) { sem_wait(&sem); char* p = "helloworld"; while(*p) { printf("%c", *p); ++p; fflush(stdout); sleep(1); } puts(""); sem_post(&sem); pthread_exit(NULL); } int main(int argc, char const *argv[]) { sem_init(&sem,0,1); pthread_t thread[5]; for (int i = 0; i < 5; ++i) { pthread_create(&thread[i], NULL, func, NULL); } for (int i = 0; i < 5; ++i) { pthread_join(thread[i], NULL); } puts(""); sem_destroy(&sem); return 0; }

stdout和stderr都是标准输出到屏幕,stdout是有缓冲区的,stderr是没有缓冲区的

4|0同步互斥方式之互斥锁

  1. 什么是互斥锁
  • 互斥锁是专门用于处理线程互斥的一种方式,它有两种状态:上锁状态/解锁状态。如果互斥锁处于上锁状态,那么再上锁会阻塞,知道这把锁解开之后,才能上锁。解锁状态依然继续解锁,不会阻塞
  1. 注意
  • 同一时刻,只有一个线程持有该锁
  • 当线程A对某一全局变量加锁访问,线程B在访问前尝试加锁,会拿不到锁,线程B阻塞。一个线程C不去加锁,直接去访问该全局变量,依然可以访问,但会出现数据混乱
  • 所以,互斥锁实际上给操作系统提供了一把“建议锁“(又称“协同锁”),建议程序中有多线程访问共享资源的使用该机制。但是没有强制限定。因此即使有了mutex锁,如果有线程不按规则来访问数据,依然会造成数据踩踏
  1. 关于线程互斥锁函数接口
  • 定义互斥锁变量----->pthread_mutex_t
    pthread_mutex_t mutex
    pthread_mutex_t类型,其本质是一个结构体。
  • 初始化互斥锁------>pthread_mutex_init()
#include <pthread.h> int pthread_mutex_init( pthread_mutex_t *mutex, // 参数1:互斥锁变量的地址 const pthread_mutexattr_t *mutexattr // 参数2:普通属性,NULL ); 返回值:成功0, 失败:非0错误码
  • 上锁------>pthread_mutex_lock()
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); // 参数1:互斥锁变量的地址 返回值:成功0, 失败:非0错误码
  • 解锁------>int pthread_mutex_unlock()
#include <pthread.h> int pthread_mutex_unlock(pthread_mutex_t *mutex); // 参数1:互斥锁变量的地址 返回值:成功0, 失败:非0错误码
  • 销毁------>int pthread_mutex_destroy()
#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex); // 参数1:互斥锁变量的地址 返回值:成功0, 失败:非0错误码
  1. lock和unlock
    lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其它线程解锁为止。unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级,调度。默认是谁先被阻塞,谁先被唤醒
    例如:P1,P2,P3,P4使用一把mutex锁。P1加锁成功,其他线程都阻塞,直到P1解锁。P1解锁后,P2,P3,P4均被唤醒,并自动
    再次尝试加锁。
    可以理解为:mutex锁init成功初值为1。lock功能是将mutex--。而unlock则将mutex++;

  2. lock和trylock
    lock加锁失败会阻塞,等待锁释放。trylock加锁失败会直接返回错误号(如:EBUSY)

实操

#include <stdio.h> #include <pthread.h> #include <unistd.h> int main_val = 0; pthread_mutex_t mutex; void* func1(void* arg) { pthread_mutex_lock(&mutex); main_val = 100; sleep(2); printf("%d\n", main_val); pthread_mutex_unlock(&mutex); pthread_exit(NULL); } void* func2(void* arg) { pthread_mutex_lock(&mutex); sleep(1); main_val = 200; printf("%d\n", main_val); pthread_mutex_unlock(&mutex); pthread_exit(NULL); } int main(int argc, char const *argv[]) { pthread_t tid1; pthread_t tid2; pthread_create(&tid1, NULL, func1, NULL); pthread_create(&tid2, NULL, func2, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); return 0; }

5|0死锁

  1. 线程试图对同一互斥量加锁两次
  2. 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁

__EOF__

本文作者若达萨罗
本文链接https://www.cnblogs.com/bcc0729/p/17669120.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   若达萨罗  阅读(42)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
点击右上角即可分享
微信分享提示