线程和进程同步互斥你真的掌握了吗?(同步互斥机制保姆级讲解与应用)
目录
同步互斥的概念
互斥锁
初始化互斥锁
销毁互斥锁
申请上锁
解锁
案例1:没有互斥锁 多任务的运行情况
案例2:有互斥锁 多任务的运行情况
死锁
读写锁
初始化读写锁
销毁读写锁
申请读锁
申请写锁
释放读写锁
案例:两个任务读 一个任务写
条件变量(重要)
概念引入
概念原理
条件变量初始化
释放条件变量
等待条件
唤醒等待在条件变量上的线程
案例:生产者和消费者
信号量
信号量的API
初始化信号量
信号量减一 P操作
信号量加一 V操作
销毁信号量
使用场景
信号量用于线程的互斥
信号量用于线程的同步
无名信号量 用于 血缘关系的进程间互斥
无名信号量 用于 血缘关系的进程间同步
有名信号量 用于 无血缘的进程间互斥
创建一个有名信号量
信号量的关闭
信号量文件的删除
案例:完成互斥
有名信号量 用于 无血缘的进程间同步
同步互斥的概念
互斥:同一时间,只能一个任务(进程或线程)执行,谁先运行不确定。
同步:同一时间,只能一个任务(进程或线程)执行,有顺序的运行。
同步 是特殊的 互斥。
互斥锁
用于线程的互斥。
互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即加锁( lock )和解锁( unlock )
互斥锁的操作流程如下:
1)在访问共享资源临界区域前,对互斥锁进行加锁。
2)在访问完成后释放互斥锁上的锁。 (解锁)
3)对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁 被释放。
互斥锁的数据类型是: pthread_mutex_t
初始化互斥锁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
功能:
初始化一个互斥锁。
参数:
mutex:互斥锁地址。类型是 pthread_mutex_t 。
attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL。
可以使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,比如:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init() 来完成动态初始
化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查。
返回值:
成功:0,成功申请的锁默认是打开的。
失败:非 0 错误码
销毁互斥锁
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资 源。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非 0 错误码
申请上锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
对互斥锁上锁,若互斥锁已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非 0 错误码
int pthread_mutex_trylock(pthread_mutex_t *mutex);
调用该函数时,若互斥锁未加锁,则上锁,返回 0;
若互斥锁已加锁,则函数直接返回失败,即 EBUSY。
解锁
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
对指定的互斥锁解锁。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非0错误码
案例1:没有互斥锁 多任务的运行情况
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>void *deal_fun01(void *arg)
{char *str = (char*)arg;while(*str!='\0'){printf("%c",*str++);//没有换行符,需要强制刷新fflush(stdout);usleep(500000);}return NULL;
}void *deal_fun02(void *arg)
{char *str = (char*)arg;while(*str!='\0'){printf("%c",*str++);//没有换行符,需要强制刷新fflush(stdout);usleep(500000);}return NULL;
}int main(int argc, char const *argv[])
{//创建两个线程pthread_t tid1,tid2;pthread_create(&tid1,NULL,deal_fun01,"hello");pthread_create(&tid2,NULL,deal_fun02,"world");pthread_detach(tid1);pthread_detach(tid2);return 0;
}
由于使用了pthread_detach,创建的线程还没来得及执行,进程就结束了,所以导致输出结果不全
使用pthread_join进行阻塞等待线程结束
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>void *deal_fun01(void *arg)
{char *str = (char*)arg;while(*str!='\0'){printf("%c",*str++);//没有换行符,需要强制刷新fflush(stdout);usleep(500000);}return NULL;
}void *deal_fun02(void *arg)
{char *str = (char*)arg;while(*str!='\0'){printf("%c",*str++);//没有换行符,需要强制刷新fflush(stdout);usleep(500000);}return NULL;
}int main(int argc, char const *argv[])
{//创建两个线程pthread_t tid1,tid2;pthread_create(&tid1,NULL,deal_fun01,"hello");pthread_create(&tid2,NULL,deal_fun02,"world");pthread_join(tid1,NULL);pthread_join(tid2,NULL);return 0;
}
案例2:有互斥锁 多任务的运行情况
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>//定义一把锁
pthread_mutex_t mutex;
void *deal_fun01(void *arg)
{char *str = (char*)arg;//上锁pthread_mutex_lock(&mutex);while(*str!='\0'){printf("%c",*str++);//没有换行符,需要强制刷新fflush(stdout);usleep(500000);}//解锁pthread_mutex_unlock(&mutex);return NULL;
}void *deal_fun02(void *arg)
{char *str = (char*)arg;//上锁pthread_mutex_lock(&mutex);while(*str!='\0'){printf("%c",*str++);//没有换行符,需要强制刷新fflush(stdout);usleep(500000);}//解锁pthread_mutex_unlock(&mutex);return NULL;
}int main(int argc, char const *argv[])
{//初始化锁pthread_mutex_init(&mutex,NULL);//创建两个线程pthread_t tid1,tid2;pthread_create(&tid1,NULL,deal_fun01,"hello");pthread_create(&tid2,NULL,deal_fun02,"world");pthread_join(tid1,NULL);pthread_join(tid2,NULL);//销毁锁pthread_mutex_destroy(&mutex);return 0;
}
由于线程是相互独立的,所以线程处理函数可以合成一个函数
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>//定义一把锁
pthread_mutex_t mutex;
void *deal_fun(void *arg)
{char *str = (char*)arg;//上锁pthread_mutex_lock(&mutex);while(*str!='\0'){printf("%c",*str++);//没有换行符,需要强制刷新fflush(stdout);usleep(500000);}//解锁pthread_mutex_unlock(&mutex);return NULL;
}int main(int argc, char const *argv[])
{//初始化锁pthread_mutex_init(&mutex,NULL);//创建两个线程pthread_t tid1,tid2;pthread_create(&tid1,NULL,deal_fun,"hello");pthread_create(&tid2,NULL,deal_fun,"world");pthread_join(tid1,NULL);pthread_join(tid2,NULL);//销毁锁pthread_mutex_destroy(&mutex);return 0;
}
总结:
如果是互斥 不管有多少个任务,只需要一把锁,所有的任务上锁 访问资源 解锁。
死锁
读写锁
POSIX 定义的读写锁的数据类型是: pthread_rwlock_t
初始化读写锁
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *rwlock,const pthread_rwlockattr_t *attr);
功能:
用来初始化 rwlock 所指向的读写锁。
参数:
rwlock:指向要初始化的读写锁指针。
attr:读写锁的属性指针。如果 attr 为 NULL 则会使用默认的属性初始化读写锁,否则 使用指定的 attr 初始化读写锁。
可以使用宏 PTHREAD_RWLOCK_INITIALIZER 静态初始化读写锁,比如:
pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER;
这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_rwlock_init() 来完成动态初 始化,不同之处在于PTHREAD_RWLOCK_INITIALIZER 宏不进行错误检查。
返回值:
成功:0,读写锁的状态将成为已初始化和已解锁。
失败:非 0 错误码。
销毁读写锁
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
功能:
用于销毁一个读写锁,并释放所有相关联的资源(所谓的所有指的是由 pthread_rwlock_init() 自动申请的资源) 。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码
申请读锁
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
功能:
以阻塞方式在读写锁上获取读锁(读锁定)。
如果没有写者持有该锁,并且没有写者阻塞在该锁上,则调用线程会获取读锁。
如果调用线程未获取读锁,则它将阻塞直到它获取了该锁。一个线程可以在一个读写锁 上多次执行读锁定。
线程可以成功调用 pthread_rwlock_rdlock() 函数 n 次,但是之后该线程必须调用
pthread_rwlock_unlock() 函数 n 次才能解除锁定。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
用于尝试以非阻塞的方式来在读写锁上获取读锁。
如果有任何的写者持有该锁或有写者阻塞在该读写锁上,则立即失败返回
申请写锁
#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
功能:
在读写锁上获取写锁(写锁定)。
如果没有写者持有该锁,并且没有写者读者持有该锁,则调用线程会获取写锁。
如果调用线程未获取写锁,则它将阻塞直到它获取了该锁。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
用于尝试以非阻塞的方式来在读写锁上获取写锁。
如果有任何的读者或写者持有该锁,则立即失败返回。
释放读写锁
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
功能:
无论是读锁或写锁,都可以通过此函数解锁。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码
案例:两个任务读 一个任务写
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//创建一把读写锁
pthread_rwlock_t rwlock;void *read_data01(void *arg)
{int *p=(int*)arg;while(1){//申请上读锁pthread_rwlock_rdlock(&rwlock);printf("任务A:num=%d\n",*p);//解读写锁pthread_rwlock_unlock(&rwlock);sleep(1);}return NULL;
}void *read_data02(void *arg)
{int *p=(int*)arg;while(1){//申请上读锁pthread_rwlock_rdlock(&rwlock);printf("任务B:num=%d\n",*p);//解读写锁pthread_rwlock_unlock(&rwlock);sleep(1);}return NULL;
}void *write_data(void *arg)
{int *p=(int*)arg;while(1){//申请上写锁pthread_rwlock_wrlock(&rwlock);(*p)++;//解读写锁pthread_rwlock_unlock(&rwlock);printf("任务C:写入num=%d\n",*p);sleep(2);}return NULL;
}int main(int argc, char const *argv[])
{//定义一个公共资源int num=0;//初始化读写锁pthread_rwlock_init(&rwlock,NULL);pthread_t tid1,tid2,tid3;pthread_create(&tid1,NULL,read_data01,&num);pthread_create(&tid2,NULL,read_data02,&num);pthread_create(&tid3,NULL,write_data,&num);pthread_join(tid1,NULL);pthread_join(tid2,NULL);pthread_join(tid3,NULL);//销毁锁pthread_rwlock_destroy(&rwlock);return 0;
}
条件变量(重要)
概念引入
对于刚刚死锁的第三种情况。如果任务A先执行上锁,由于管道没有数据,所以读不到数据会一直阻塞,导致无法解锁;任务A无法解锁就会导致任务B无法上锁,也就无法向管道写入数据,同样会导致任务B阻塞等待上锁。
我们按照进程管道通信的方法,可以使用非阻塞读取来解决这个死锁问题。
但我们还可以通过条件变量来进行解决,任务A在执行到读数据的时候,发现管道里没有数据,就会通过条件变量临时解锁,从而通知任务B成功上锁进行数据写入。
概念原理
条件变量是用来等待条件满足而不是用来上锁的,条件变量本身不是锁。
条件变量和互斥锁同时使用。
条件变量的两个动作: 条件不满, 阻塞线程。当条件满足, 通知阻塞的线程开始工作。
条件变量的类型: pthread_cond_t。
原子操作: 几个操作连续进行,不可分割,不能被其它代码操作插入和打断。
条件变量初始化
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond,
const pthread_condattr_t *attr);
功能:
初始化一个条件变量
参数:
cond:指向要初始化的条件变量指针。
attr:条件变量属性,通常为默认值,传NULL即可 。
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
返回值:
成功:0
失败:非0错误号
释放条件变量
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
功能:
销毁一个条件变量
参数:
cond:指向要初始化的条件变量指针
返回值:
成功:0
失败:非0错误号
等待条件
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex);
功能:
阻塞等待一个条件变量
如果阻塞就 先解锁、等待条件满足、重新上锁(3步为原子操作)解阻塞
参数:
cond:指向要初始化的条件变量指针
mutex:互斥锁
返回值:
成功:0
失败:非0错误号
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,const struct *abstime);
功能:
限时等待一个条件变量
参数:
cond:指向要初始化的条件变量指针
mutex:互斥锁
abstime:绝对时间
返回值:
成功:0
失败:非0错误号
唤醒等待在条件变量上的线程
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
功能:
唤醒至少一个阻塞在条件变量上的线程
参数
cond:指向要初始化的条件变量指
返回值
成功:0
失败:非0错误号
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:
唤醒全部阻塞在条件变量上的线程
参数:
cond:指向要初始化的条件变量指针
返回值:
成功:0
失败:非0错误号
案例:生产者和消费者
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//定义互斥锁
pthread_mutex_t mutex;
//定义条件变量
pthread_cond_t cond;
//定义一个仓库 默认有三个产品
int num = 3;void *comsumption_fun(void *arg) //消费
{while (1){//申请上锁pthread_mutex_lock(&mutex);//判断仓库是否为空 等待条件变量满足if (0 == num) //仓库为空{printf("%s 发现仓库为空,等待生产\n", (char *)arg);pthread_cond_wait(&cond, &mutex);}else{//进入仓库购买产品num--;printf("%s 购买了一个产品,仓库剩余%d个\n", (char *)arg, num);//使用产品printf("%s 正在使用产品\n", (char *)arg);}//解锁pthread_mutex_unlock(&mutex);sleep(rand() % 5);}
}void *production_fun(void *arg) //生产
{while (1){//生产一个产品sleep(rand() % 5);//上锁 进入仓库pthread_mutex_lock(&mutex);//将产品放入仓库num++;printf("%s 放入一个产品,仓库剩余%d\n", (char *)arg, num);//通知 条件变量阻塞的线程pthread_cond_broadcast(&cond);//解锁pthread_mutex_unlock(&mutex);}
}
int main(int argc, char const *argv[])
{//设计随机数种子srand(time(NULL));//初始化锁pthread_mutex_init(&mutex, NULL);//初始化条件变量pthread_cond_init(&cond, NULL);pthread_t tid1, tid2, tid3;pthread_create(&tid1, NULL, comsumption_fun, "消费者A");pthread_create(&tid2, NULL, comsumption_fun, "消费者B");pthread_create(&tid3, NULL, production_fun, "生产者");//等待线程结束pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_join(tid3, NULL);//销毁锁pthread_mutex_destroy(&mutex);//销毁条件变量pthread_cond_destroy(&cond);return 0;
}
信号量
信号量也叫信号灯,其本质就是一个计数器,描述临界资源的数目大小。(最多能有多少资源分配给线程)。
信号量可参考:信号量的概念 及其 操作函数(有名、无名信号量)_仲夏夜之梦~的博客-CSDN博客
前面的锁和条件变量只用于线程间同步互斥。而信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被 用来控制对公共资源的访问。
适用于六种情况:线程间的同步与互斥、有血缘进程间的同步与互斥、无血缘进程间的同步与互斥。
当信号量值大于 0 时,则可以访问,否则将阻塞。
PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。
号量数据类型为:sem_t
信号量用于互斥:
不管多少个任务互斥 只需要一个信号量(与前面的锁机制相同)。先P 任务 再 V
信号量用于同步:
有多少个任务 就需要多少个信号量。最先执行的任务对应的信号量为1,其他信号量全 部为0。
每任务先P自己 任务 再V下一个任务的信号量
信号量的API
初始化信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value)
功能:
创建一个信号量并初始化它的值。一个无名信号量在被使用前必须先初始化。
参数:
sem:信号量的地址
pshared:等于 0,信号量在线程间共享(常用);不等于0,信号量在进程间共享。
value:信号量的初始值
返回值:
成功:0
失败: - 1
信号量减一 P操作
int sem_wait(sem_t *sem);
功能: 将信号量减一,如果信号量的值为0 则阻塞,大于0可以减一
参数:信号量的地址
返回值:成功返回0 失败返回-1
尝试对信号量减一
int sem_trywait(sem_t *sem);
功能: 尝试将信号量减一,如果信号量的值为0 不阻塞,立即返回 ,大于0可以减一
参数:信号量的地址
返回值:成功返回0 失败返回-1
信号量加一 V操作
int sem_post(sem_t *sem);
功能:将信号量加一
参数:信号量的地址
返回值:成功返回0 失败返回-1
销毁信号量
int sem_destroy(sem_t *sem);
功能: 销毁信号量
参数: 信号量的地址
返回值:成功返回0 失败返回-1
使用场景
信号量用于线程的互斥
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>//定义一个信号量(用于互斥)
sem_t sem;void my_print(char* str)
{int i=0;while(str[i]!='\0'){printf("%c",str[i++]);fflush(stdout);sleep(1);}return;
}void *task_fun(void *arg)
{//P操作sem_wait(&sem);my_print((char*)arg);//V操作sem_post(&sem);return NULL;
}int main(int argc, char const *argv[])
{//信号量初始化为1sem_init(&sem,0,1);pthread_t tid1,tid2,tid3;pthread_create(&tid1,NULL,task_fun,"hello");pthread_create(&tid2,NULL,task_fun,"world");pthread_create(&tid3,NULL,task_fun,"nanjing");pthread_join(tid1,NULL);pthread_join(tid2,NULL);pthread_join(tid3,NULL);//销毁信号量sem_destroy(&sem);return 0;
}
信号量用于线程的同步
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>//定义一个信号量(用于互斥)
sem_t sem1,sem2,sem3;void my_print(char* str)
{int i=0;while(str[i]!='\0'){printf("%c",str[i++]);fflush(stdout);sleep(1);}return;
}void *task_fun01(void *arg)
{//P操作sem_wait(&sem2);my_print((char*)arg);//V操作sem_post(&sem3);return NULL;
}void *task_fun02(void *arg)
{//P操作sem_wait(&sem3);my_print((char*)arg);//V操作sem_post(&sem1);return NULL;
}void *task_fun03(void *arg)
{//P操作sem_wait(&sem1);my_print((char*)arg);//V操作sem_post(&sem2);return NULL;
}int main(int argc, char const *argv[])
{//信号量初始化为1sem_init(&sem1,0,1);sem_init(&sem2,0,0);sem_init(&sem3,0,0);pthread_t tid1,tid2,tid3;pthread_create(&tid1,NULL,task_fun01,"hello");pthread_create(&tid2,NULL,task_fun02,"world");pthread_create(&tid3,NULL,task_fun03,"nanjing");pthread_join(tid1,NULL);pthread_join(tid2,NULL);pthread_join(tid3,NULL);//销毁信号量sem_destroy(&sem1);sem_destroy(&sem2);sem_destroy(&sem3);return 0;
}
无名信号量 用于 血缘关系的进程间互斥
如下图所示,通过一个普通的信号量想要实现父子进程间的互斥,由于父子进程使用的是独立的代码空间,因此当fork后,两个进程都会执行PV操作,因此这种方法无法实现进程互斥。
我们可以保证信号量是父子进程公共识别的,让信号量脱离父子进程空间。我们可以通过磁盘映射、存储映射、内存共享
#include <sys/mman.h>
#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/wait.h>void my_print(char *str)
{int i = 0;while (str[i] != '\0'){printf("%c", str[i++]);fflush(stdout);sleep(1);}return;
}int main(int argc, char const *argv[])
{//定义一个无名信号量// MAP_ANONYMOUS匿名映射(不使用文件描述符),fd为-1表示不需要文件描述符sem_t *sem = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);//无名信号量的初始化 参数2:1表示进程sem_init(sem, 1, 1);pid_t pid = fork();if (pid == 0) //子进程{// P操作sem_wait(sem);my_print("hello");// V操作sem_post(sem);}else if (pid > 0) //父进程{// P操作sem_wait(sem);my_print("world");// V操作sem_post(sem);int status = 0;pid_t pid = wait(&status);}//销毁信号量sem_destroy(sem);
}
无名信号量 用于 血缘关系的进程间同步
进程间的同步需要用到两个信号量
#include <sys/mman.h>
#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/wait.h>void my_print(char *str)
{int i = 0;while (str[i] != '\0'){printf("%c", str[i++]);fflush(stdout);sleep(1);}return;
}int main(int argc, char const *argv[])
{//定义一个无名信号量// MAP_ANONYMOUS匿名映射(不使用文件描述符),fd为-1表示不需要文件描述符sem_t *sem1 = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);sem_t *sem2 = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);//无名信号量的初始化 参数2:1表示进程sem_init(sem1, 1, 1);sem_init(sem2, 1, 0);pid_t pid = fork();if (pid == 0) //子进程{// P操作sem_wait(sem1);my_print("hello");// V操作sem_post(sem2);}else if (pid > 0) //父进程{// P操作sem_wait(sem2);my_print("world");// V操作sem_post(sem1);int status = 0;pid_t pid = wait(&status);}//销毁信号量sem_destroy(sem1);sem_destroy(sem2);
}
有名信号量 用于 无血缘的进程间互斥
有名信号量的使用类似于文件操作,有名信号量创建完成之后,当前整个系统有效,直到系统重启或通过sem_unlink函数手动删除。
创建一个有名信号量
创建并初始化有名信号量或打开一个已存在的有名信号量。
注: 如果指定了O_CREAT(而没有指定O_EXCL),那么只有所需的信号量尚未存在时才初始化他。 如果所需的信号量已经存在,mode,value都会被忽略。
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
//信号量存在
sem_t *sem_open(const char *name, int oflag);
//信号量不存在
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
功能:
创建一个信号量
参数:
name:信号量的名字
oflag:sem_open函数的权限标志
mode:文件权限(可读、可写、可执行 0777)的设置
value:信号量的初始值
返回值:
信号量的地址,失败:SEM_FAILED
信号量的关闭
int sem_close(sem_t *sem);
功能:关闭信号量
参数:信号量的的地址
返回值:成功0 失败-1
信号量文件的删除
#include <semaphore.h>
int sem_unlink(const char *name);
功能:删除信号量的文件
参数:信号量的文件名
返回值:成功0 失败-1
案例:完成互斥
TaskA.c
#include <stdio.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>void my_print(char *str)
{int i = 0;while (str[i] != '\0'){printf("%c", str[i++]);fflush(stdout);sleep(1);}return;
}int main(int argc, char const *argv[])
{//创建一个有名信号量sem_open类似文件操作 最后一个参数为初始值sem_t *sem = sem_open("sem",O_RDWR|O_CREAT,0666,1);//P 操作sem_wait(sem);//任务my_print("hello world");//V 操作sem_post(sem);//关闭信号量sem_close(sem);//销毁信号量sem_destroy(sem);return 0;
}
TaskB.c
#include <stdio.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>void my_print(char *str)
{int i = 0;while (str[i] != '\0'){printf("%c", str[i++]);fflush(stdout);sleep(1);}return;
}int main(int argc, char const *argv[])
{//创建一个有名信号量sem_open类似文件操作 最后一个参数为初始值sem_t *sem = sem_open("sem",O_RDWR|O_CREAT,0666,1);//P 操作sem_wait(sem);//任务my_print("nanjing 8.20");//V 操作sem_post(sem);//关闭信号量sem_close(sem);//销毁信号量sem_destroy(sem);return 0;
}
运行效果:先运行A,再运行B。A关闭有名信号量前,B无法打开有名信号量(阻塞)
有名信号量 用于 无血缘的进程间同步
TaskA.c
#include <stdio.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>void my_print(char *str)
{int i = 0;while (str[i] != '\0'){printf("%c", str[i++]);fflush(stdout);sleep(1);}return;
}int main(int argc, char const *argv[])
{//创建一个有名信号量sem_open类似文件操作 最后一个参数为初始值sem_t *sem1 = sem_open("sem1",O_RDWR|O_CREAT,0666,1);sem_t *sem2 = sem_open("sem2",O_RDWR|O_CREAT,0666,0);//P 操作sem_wait(sem1);//任务my_print("hello world");//V 操作sem_post(sem2);//关闭信号量sem_close(sem1);sem_close(sem2);//销毁信号量sem_destroy(sem1);sem_destroy(sem2);return 0;
}
TaskB.c
#include <stdio.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>void my_print(char *str)
{int i = 0;while (str[i] != '\0'){printf("%c", str[i++]);fflush(stdout);sleep(1);}return;
}int main(int argc, char const *argv[])
{//创建一个有名信号量sem_open类似文件操作 最后一个参数为初始值sem_t *sem1 = sem_open("sem1",O_RDWR|O_CREAT,0666,1);sem_t *sem2 = sem_open("sem2",O_RDWR|O_CREAT,0666,0);//P 操作sem_wait(sem2);//任务my_print("nanjing 8.20");//V 操作sem_post(sem1);//关闭信号量sem_close(sem1);sem_close(sem2);//销毁信号量sem_destroy(sem1);sem_destroy(sem2);return 0;
}
相关文章:
线程和进程同步互斥你真的掌握了吗?(同步互斥机制保姆级讲解与应用)
目录 同步互斥的概念 互斥锁 初始化互斥锁 销毁互斥锁 申请上锁 解锁 案例1:没有互斥锁 多任务的运行情况 案例2:有互斥锁 多任务的运行情况 死锁 读写锁 初始化读写锁 销毁读写锁 申请读锁 申请写锁 释放读写锁 案例:两个任务…...
Android 9.0 Vold挂载流程解析(上)
前言 我们分2篇文章来介绍Android 9.0中存储卡的挂载流程,本篇文章先介绍总体的挂载模块、Vold进程的入口main函数的详细分析,有了这些基础知识,下一篇中我们再详细介绍收到驱动层消息是怎么挂载和卸载存储卡的,还有framework层如…...
界面组件Telerik UI for WinForms R2 2023——拥有VS2022暗黑主题
Telerik UI for WinForms拥有适用Windows Forms的110多个令人惊叹的UI控件。所有的UI for WinForms控件都具有完整的主题支持,可以轻松地帮助开发人员在桌面和平板电脑应用程序提供一致美观的下一代用户体验。 Telerik UI for WinForms R2 2023于今年6月份发布&…...
vue+elementui 实现文本超出长度显示省略号,鼠标移上悬浮展示全部内容
一、场景 表单内的输入框一般为固定宽度,当输入框内容长度超出输入框宽度时,需要显示省略号,并设置鼠标移到输入框上时悬浮展示全部内容。 <el-tooltipplacement"top-start"effect"light":content"basicData[Or…...
【STM32RT-Thread零基础入门】 5. 线程创建应用(线程创建、删除、初始化、脱离、启动、睡眠)
硬件:STM32F103ZET6、ST-LINK、usb转串口工具、4个LED灯、1个蜂鸣器、4个1k电阻、2个按键、面包板、杜邦线 文章目录 前言一、线程管理接口介绍二、任务:使用多线程的方式同时实现led闪烁和按键控制喇叭(扫描法)1. RT-Thread相关接…...
计算机竞赛 python+深度学习+opencv实现植物识别算法系统
0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于深度学习的植物识别算法研究与实现 🥇学长这里给一个题目综合评分(每项满分5分) 难度系数:4分工作量:4分创新点:4分 🧿 更多…...
深度探索ChatGPT:如何进行专业提问以获取精确答案
ChatGPT,作为OpenAI的先锋,已经展示出其惊人的交流和理解能力。但如何才能充分利用其潜能,并与之进行更深入、更专业的交流呢? 下面,我们将从专业的角度探讨一些提问策略,并附上实际案例,让你更加熟练地与…...
1.vue3+vite开发中axios使用及跨域问题解决
一、跨域问题解决 1.基于vitevue3配置时,在vite.congig.js文件server项目中添加 proxy代理 文件名:vite.congig.js server: {open: true,//启动项目自动弹出浏览器port: 3000,proxy: {/api: {target: http://localhost:8000/api/,changeOrigin: true,rew…...
【LangChain】P1 LangChain 应用程序的核心构建模块 LLMChain 以及其三大部分
LangChain 的核心构建模块 LLMChain LangChain 应用程序的核心构建模块语言模型 - LLMs提示模板 - Prompt templates输出解析器 - Output Parsers LLMChain 组合 LangChain 应用程序的核心构建模块 LangChain 应用程序的核心构建模块 LLMChain 由三部分组成: 语言…...
关于查看处理端口号和进程[linux]
查看端口号 lsof -i:端口号如果-bash: lsof: 未找到命令那我们可以执行yum install lsof 删除端口号进程 一般我们都会使用kill命令 kill -l#列出所有可用信号1 (HUP):重新加载进程。9 (KILL):杀死一个进程。15 (TERM):正常停止一个进程。 …...
C 语言的 strcat() 函数和 strncat() 函数
文章目录 strcat() 函数strncat() 函数 strcat() 函数 原型: char *strcat(char *dest, const char *src) 参数: dest – 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串。 src – 指向要追加的字符串,该字符串不会覆…...
C++ string 的用法
目录 string类string类接口函数及基本用法构造函数,析构函数及赋值重载函数元素访问相关函数operator[]atback和front 迭代器iterator容量操作size()和length()capacity()max_sizeclearemptyreserveresizeshrink_to_fit string类对象修改操作operatorpush_backappen…...
MyBatis-Flex学习记录1---请各位大神指教
简介(官网介绍) MyBatis-Flex 是一个优雅的 MyBatis 增强框架,它非常轻量、同时拥有极高的性能与灵活性。我们可以轻松的使用 Mybaits-Flex 链接任何数据库,其内置的 QueryWrapper帮助我们极大的减少了 SQL 编写的工作的同时&…...
二分查找旋转数组
已知整数数组nums,先按升序排序后,再旋转。旋转k位后,元素分别为nums[k],nums[k1]...nums[0]...nums[k-1]。请查找target 是否存在,如果存在返回所在索引;否则返回-1。假定nums没有重复的元素。 假定排序后的数组为{1…...
关于3D位姿旋转
一. 主动旋转和被动旋转 1. active rotation 主动旋转 站在坐标系的位置看旋转目标物:目标物主动发生旋转。 2. passive rotation 被动旋转 站在旋转目标物的位置看坐标系: 坐标系发生旋转,相当于目标物在坐标系内的位置被动地发生了旋转…...
解锁项目成功的关键:项目经理的结构化思维之道
1. 项目经理的核心职责 作为项目经理,我们的工作不仅仅是跟踪进度和管理团队。我们的角色在整个项目生命周期中都是至关重要的,从初始概念到最终交付。以下是项目经理的几个核心职责: 确保项目目标的清晰性项目的成功在很大程度上取决于其目…...
力扣974被K整除的子数组
同余定理 使用前缀和哈希表 由于可能是负数所以要进行修正:(sum%kk)%k class Solution { public:int subarraysDivByK(vector<int>& nums, int k) {unordered_map<int,int> hash;hash[0 % k] 1; //0 这个数的余数int sum 0, ret 0;for(auto x…...
简单认识Docker数据管理
文章目录 为何需要docker数据管理数据管理类型 一、数据卷二、数据卷容器三、容器互联 为何需要docker数据管理 因为数据写入后如果停止了容器,再开启数据就会消失,使用数据管理的数据卷挂载,实现了数据的持久化,重启数据还会存在…...
UDP数据报结构分析(面试重点)
在传输层中有UDP和TCP两个重要的协议,下面将针对UDP数据报的结构进行分析 UDP结构图示 UDP报头结构的分析 UDP报头有4个属性,分别是源端口,目的端口,UDP报文长度,校验和,它们都占16位2个字节,所…...
【Java 动态数据统计图】动态数据统计思路案例(动态,排序,数组)二(113)
需求: 有一个List<Map<String.Object>>,存储了区域的数据, 数据是根据用户查询条件进行显示的;所以查询的数据是动态的;按区域维度统计每个区域出现的次数,并且按照次数的大小排序(升序&#…...
C++进阶 类型转换
本文简介:介绍C中类型转换的方式 类型转换 C语言中的类型转换为什么C需要四种类型转换C强制类型转换static_castreinterpret_castconst_castdynamic_cast RTTI(了解)总结 C语言中的类型转换 在C语言中,如果赋值运算符左右两侧类型…...
Idea中隐藏指定文件或指定类型文件
Setting ->Editor ->Code Style->File Types → Ignored Files and Folders输入要隐藏的文件名,支持*号通配符回车确认添加...
第2步---MySQL卸载和图形化工具展示
第2步---MySQL卸载和图形化工具展示 1.MySQL的卸载 2.MySQL的图形化工具 2.1常见的图形化工具 SQLyog:简单。SQLyog首页、文档和下载 - MySQL 客户端工具 - OSCHINA - 中文开源技术交流社区 Mysql Workbench :MySQL :: MySQL Workbench DataGrip&…...
原型和原型链
好久没记了有点忘记了,来记录一下。 1、函数和对象的关系:对象都是通过函数创建的,函数也是一个对象。 2、原型和原型链 1.原型:原型分为两种 prototype:每一个函数都会有prototype属性,它指向函数的原型…...
解决ios隔空播放音频到macos没有声音的问题
解决ios隔空播放音频到macos没有声音的问题 一、检查隔空播放支持设备和系统要求二、打开隔空播放接收器三、重置MAC控制中心进程END 一、检查隔空播放支持设备和系统要求 Mac、iPhone、iPad 和 Apple Watch 上“连续互通”的系统要求 二、打开隔空播放接收器 ps;我设备是同一…...
LTPP在线开发平台【使用教程】
LTPP在线开发平台 点击访问 LTPP在线开发平台 LTPP(Learning teaching practice platform)在线开发平台是一个编程学习网站,该网站集文章学习、短视频、在线直播、代码训练、在线问答、在线聊天和在线商店于一体,专注于提升用户编…...
0818 新增码表 git拉取代码
目的是新增两个码表字段。然后和前端联调。 use db; delete from sys_dict_data where dict_type res_switch_status; INSERT INTO sys_dict_data VALUES (0, 1, 已接入, 1, res_switch_status, NULL, default, N, 0, , 2022-07-26 10:43:41, , NULL, NULL); INSERT INTO sys…...
AI 绘画Stable Diffusion 研究(十)sd图生图功能详解-精美二维码的制作
免责声明: 本案例所用安装包免费提供,无任何盈利目的。 大家好,我是风雨无阻。 为了让大家更直观的了解图生图功能,明白图生图功能到底是干嘛的,能做什么事情?今天我们继续介绍图生图的实用案例-精美二维码的制作。 对…...
C# File.ReadAllLines()报错
项目中需要读取一个文本文件的内容,调用C#的File.ReadAllLines(path)方法,但是报错,就提示unknown exception,也没其他提示了。 文件是在的,并且,如果把文件拷贝到另外一个路径,再次读取是正常…...
LeetCode 1162. As Far from Land as Possible【多源BFS】中等
本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…...
外国网站签到做任务每月挣钱/深圳最好的外贸seo培训
层叠样式 表和动态HTML层叠样式表(CSS)是指定HTML文档或XML文档的表现的标准。使用CSS和Javascript,可以创建出各种视觉效果,这些效果可以统称为动态HTML(DHTML)CSS样式是一个名称/值的属性列表指定的,属性之间用分号隔开,名字属性…...
学校网站模板下载/兰蔻搜索引擎营销案例
转自:https://blog.csdn.net/xzmeasy/article/details/75103431 为什么要用jspf文件 写jsp页面时,是不是:css和js引用特别多,而且有些页面头部信息包括css、js引用都一样,这时候聪明的程序猿肯定会想到把这一部分抽取出…...
广州 建设 招聘信息网站/我赢网seo优化网站
🍅 作者简介:哪吒,CSDN2021博客之星亚军🏆、新星计划导师✌、博客专家💪 🍅 哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师 🍅 关注公众号【哪吒编程】,回复1024,获取Java学习路线思维导图、大厂面试真题、加入万粉计划交流群、一起学习进步 目录 一…...
网页设计师培训排行榜/seo站长工具查询
作者 彭东林pengdonglin137163.com平台 Linux-4.10.17Qemu2.8 vexpress-a9概述 在系统开机probe驱动的时候,有些设备驱动加载可能需要比较长的时间,尤其是像i2c这样的设备,总线速率较低,如果在probe时读写大量的寄存器的话&#…...
免费网站做seo/seo外贸网站制作
传送门 floyd求最小环的板子题目。 就是枚举两个相邻的点求最小环就行了。 代码: #include<bits/stdc.h> #define inf 0x3f3f3f3f3f3f #define ll long long using namespace std; inline ll read(){ll ans0;char chgetchar();while(!isdigit(ch))chgetcha…...
天津国际工程建设监理公司网站/品牌营销推广方案
发了vue2.0之axios使用详解(一)后,有朋友问如何在实际项目中使用,下面把我平常用的两种方法分享下,自己在实际项目中总结的方法,有不好的地方还请指正,共同提高,谢谢! [javascript] view plainc…...