2024-02-28-物联网系统编程(9-多任务互斥与同步)
9.多任务互斥与同步
9.1 互斥与同步概述
在多任务操作系统中,同时运行的多个任务可能都需要访问/使用同一种资源多个任务之间有依赖关系,某个任务的运行依赖于另一个任务,同步和互斥就是用于解决这两个问题的。
互斥:
一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。POSIX 标准中进程和线程同步和互斥的方法,主要有信号量和互斥锁两种方式。
同步:
两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。
9.2 互斥锁
9.2.1 互斥锁的概念
mutex 是一种简单的加锁的方法来控制对共享资源的访问,mutex只有两种状态,即上锁(lock)和解锁(unlock)。
在访问该资源前,首先应申请 mutex,如果 muex处于unlock 状态,则会申请到 mutex,并立即lock;如果 mutex 处于 ;lock 状态,则默认阻塞申请者。
unlock 操作应该由 lock 者进行。
9.2.2 初始化互斥锁
mutex用pthread_mutex_t
数据类型表示,在使用互斥锁前,必须先对它进行初始化。
静态分配的互斥锁
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER
动态分配互斥锁
pthread_mutext_mutex; pthread_mutex_init(&mutex,NULL);
在所有使用过此互斥锁的线程都不再需要使用时候,应调用销毁互斥锁
销毁互斥锁
pthread_mutex_destroy // 销毁互斥锁。
#include<pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr); 功能: 初始化一个互斥锁。 参数: mutex:指定的互斥锁 mutexattr:互斥锁的属性,为NULL表示默认属性; 返回值: 成功:0; 失败:非0
9.2.3 互斥锁上锁
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); 功能:对互斥锁上锁,若已经上锁,则调用者一直阻塞到互斥锁解锁; 参数: mutex:指定的互斥锁; 返回值: 成功:0 失败:非0
#include <pthread.h> int pthread_mutex_trylock(pthread_mutex_t *mutex); 功能:对互斥锁上锁,若已经上锁,则上锁失败,函数立即返回; 参数: mutex:互斥锁地址; 返回值: 成功:0 失败:非0。
9.2.4 互斥锁解锁
#include <pthread.h> int pthread_mutex_unlock(pthread_mutext* mutex); 功能:对指定的互斥锁解锁。 参数: mutex:互斥锁地址; 返回值: 成功:0 失败:非0
9.2.5 销毁互斥锁
#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex); 功能:销毁指定的一个互斥锁; 参数: mutex:互斥锁地址 返回值: 成功:0 失败:非0
9.3 互斥锁案例
9.3.1 不使用互斥锁的结果
张三和李四去银行取钱,张三是主卡,李四为副卡,卡内总金额为10000元
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> int money = 10000; void *pthread_fun1(void *arg) { int get, yu, shiji; get = 10000; printf("张三正在查询余额...\n"); sleep(1); yu = money; printf("张三正在取钱\n"); sleep(1); if (get > yu) { shiji = 0; } else { shiji = get; yu = yu - get; money = yu; } printf("张三想取%d元,实际取了%d元,余额为%d元\n", get, shiji, yu); pthread_exit(NULL); } void *pthread_fun2(void *arg) { int get, yu, shiji; get = 10000; printf("李四正在查询余额...\n"); sleep(1); yu = money; printf("李四正在取钱\n"); sleep(1); if (get > yu) { shiji = 0; } else { shiji = get; yu = yu - get; money = yu; } printf("李四想取%d元,实际取了%d元,余额为%d元\n", get, shiji, yu); pthread_exit(NULL); } int main(int argc, char const *argv[]) { pthread_t thread1, thread2; if (pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0) { perror("fail to pthread_create\n"); exit(1); } if (pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0) { perror("fail to pthread_create\n"); exit(1); } pthread_join(thread1, NULL); pthread_join(thread2, NULL); return 0; }
输出结果
张三正在查询余额... 李四正在查询余额... 李四正在取钱 张三正在取钱 李四想取10000元,实际取了10000元,余额为0元 张三想取10000元,实际取了10000元,余额为0元
9.3.2 使用互斥锁
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> // 通过互斥锁解决线程间互斥问题 int money = 10000; // 第一步,创建互斥锁 pthread_mutex_t mymutex; void *pthread_fun1(void *arg) { int get, yu, shiji; get = 10000; // 第三步,对共享资源的操作进行上锁 pthread_mutex_lock(&mymutex); printf("张三正在查询余额...\n"); sleep(1); yu = money; printf("张三正在取钱\n"); sleep(1); if (get > yu) { shiji = 0; } else { shiji = get; yu = yu - get; money = yu; } printf("张三想取%d元,实际取了%d元,余额为%d元\n", get, shiji, yu); // 第四步,当共享资源操作执行完毕后,对互斥锁解锁 pthread_mutex_unlock(&mymutex); pthread_exit(NULL); } void *pthread_fun2(void *arg) { int get, yu, shiji; get = 10000; pthread_mutex_lock(&mymutex); printf("李四正在查询余额...\n"); sleep(1); yu = money; printf("李四正在取钱\n"); sleep(1); if (get > yu) { shiji = 0; } else { shiji = get; yu = yu - get; money = yu; } printf("李四想取%d元,实际取了%d元,余额为%d元\n", get, shiji, yu); pthread_mutex_unlock(&mymutex); pthread_exit(NULL); } int main(int argc, char const *argv[]) { pthread_t thread1, thread2; // 第二步,初始化互斥锁 pthread_mutex_init(&mymutex, NULL); if (pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0) { perror("fail to pthread_create\n"); exit(1); } if (pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0) { perror("fail to pthread_create\n"); exit(1); } pthread_join(thread1, NULL); pthread_join(thread2, NULL); // 第五步, 互斥锁使用完毕,销毁互斥锁 pthread_mutex_destroy(&mymutex); return 0; }
输出结果
李四正在查询余额... 李四正在取钱 李四想取10000元,实际取了10000元,余额为0元 张三正在查询余额... 张三正在取钱 张三想取10000元,实际取了0元,余额为0元
9.4 信号量
9.3.1 信号量概念
信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于0时则可以访问,否则将阻塞。
信号量又称之为PV操作,PV原语是对信号量的操作,一次P操作使信号量sem减1,一次V操作使信号量sem加1。对于P操作,如果信号量的sem值为小于等于0,则P操作就会阻塞,如果信号量的值大于0,才可以执行P操作进行减1。
信号量主要用于进程或线程间的同步和互斥这两种典型情况
- 若用于互斥,几个进程(或线程)往往只设置一个信号量。
- 若用于同步操作,往往会设置多个信号量,并且安排不同的初始值,来实现它们之间的执行顺序。
信号量用于互斥

信号量用于同步

9.3.2 信号量的操作
信号量初始化
#include <semaphore.h> int sem_init(sem t *sem, int pshared,unsigned int value); 功能: 创建一个信号量并初始化它的值 参数: sem:信号量的地址。 pshared:等于0,信号量在线程间共享;不等于0,信号量在进程间共享; value:信号量的初始值; 返回值: 成功:返回0; 失败:返回-1.
信号量P操作
#include <semaphore.h> int sem_wait(sem_t *sem); 功能: 将信号量的值减 1,若信号量的值小于等于0,此函数会引起调用者阻塞; 参数: sem:信号量地址; 返回值: 成功返回 0; 失败返回-1。
#include <semaphore.h> int sem trywait(sem t *sem) 功能: 将信号量的值减上,若信号量的值小于等于 0,则对信号量的操作失败,函数立即返回; 参数: 信号量地址: sem; 返回值: 成功返回0,失败返回-1.
信号量V操作
#include <semaphore.h> int sem_post(sem t*sem); 功能:执行V操作,执行一次,信号量的值加1; 参数: sem:指定的信号量; 返回值: 成功:0 失败:-1
获取信号量的计数值
#include <semaphore.h> int sem_getvalue(sem_t *sem,int *sval); 功能:获取sem标识的信号量的值,保存在sval中; 参数: sem:信号量地址。 sval:保存信号量值的地址; 返回值: 成功返回0,失败返回-1。
信号量的销毁
#include <semaphore.h> int sem_destroy(sem_t *sem) 功能: 删除 sem标识的信号量; 参数: sem:信号量地址; 返回值: 成功返回 0,失败返回-1。
9.3.3 信号量的使用
信号量实现互斥
需要实现打印机效果的helloworld或者worldhello,不用信号量的代码如下
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h> void printer(char *str) { while (*str) { putchar(*str); fflush(stdout); str++; sleep(1); } } void *thread_fun1(void *arg) { char *str1 = "hello"; printer(str1); } void *thread_fun2(void *arg) { char *str2 = "world"; printer(str2); } int main(int argc, char const *argv[]) { pthread_t tid1, tid2; pthread_create(&tid1, NULL, thread_fun1, NULL); pthread_create(&tid2, NULL, thread_fun2, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); printf("\n"); return 0; }
输出结果
hweolrllod
信号量实现互斥
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h> // 通过信号量实现互斥操作 // 第一个:创建一个信号量 sem_t sem; void printer(char *str) { // 由于使用信号量实现互斥,信号量初始值设置为1,则两个线程执行P操作 // 先执行P操作的线程继续执行,后执行p操作的先阻塞等待 // 第三步 执行P操作 sem_wait(&sem); while (*str) { putchar(*str); fflush(stdout); str++; sleep(1); } // 第四步 执行V操作 sem_post(&sem); } void *thread_fun1(void *arg) { char *str1 = "hello"; printer(str1); } void *thread_fun2(void *arg) { char *str2 = "world"; printer(str2); } int main(int argc, char const *argv[]) { // 初始化信号量 // 0 - 线程间共享,第二个参数 // 1 - 互斥,第三个参数 sem_init(&sem, 0, 1); pthread_t tid1, tid2; pthread_create(&tid1, NULL, thread_fun1, NULL); pthread_create(&tid2, NULL, thread_fun2, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); printf("\n"); // 第五步 信号量销毁 sem_destroy(&sem); return 0; }
输出结果
helloworld
信号量实现同步
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h> // 使用信号量实现同步功能 // 第一步 如果两个线程同步,则需要两个信号量 sem_t sem_g, sem_p; char ch = 'a'; void *thread_g(void *arg) { while (1) { // 第四步 后执行的线程中,将信号量初始值设置为0的信号量,执行P操作 sem_wait(&sem_g); ch++; sleep(1); // 第六步 后执行的线程执行完毕后,将信号量初始值为1的信号量执行V操作 sem_post(&sem_p); } } void *thread_p(void *arg) { while (1) { // 第三步,先执行的线程中,将信号量初始值设置为1的信号量执行P操作 sem_wait(&sem_p); printf("%c", ch); fflush(stdout); // 第五步,当先执行的线程执行完毕后,需要将信号量初始值为0的信号量执行V操作 sem_post(&sem_g); } } int main(int argc, char const *argv[]) { // 第二步 初始化信号量 // 0 - 线程间共享,第二个参数 // 0 - 同步,第三个参数,sem_g先执行,设置为0 // sem_p后执行,设置为1 sem_init(&sem_g, 0, 0); sem_init(&sem_p, 0, 1); pthread_t tid1, tid2; pthread_create(&tid1, NULL, thread_g, NULL); pthread_create(&tid2, NULL, thread_p, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); printf("\n"); // 第八步 信号量销毁 sem_destroy(&sem_g); sem_destroy(&sem_p); return 0; }
输出结果
abcdefghijklmnopqrstuv
本文来自博客园,作者:Yasuo_Hasaki,转载请注明原文链接:https://www.cnblogs.com/hasaki-yasuo/p/18040610
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步