系统编程-同步互斥机制
同步互斥机制
-
同步概念
所谓同步,即同时起步,协调一致。不同对象,对于“同步”的理解方式不一样(略有不同)。如设备同步,是指在两个设备之间规定一个共同的时间作为参考;数据同步,是指让两个或多个数据库内容保持一致,或者按需要部分保持一致;文件同步,是指两个或者多个文件夹的文件保持一致等等
但是在编程中,通信中说的同步与生活中大家印象中同步是不一样的。“同”,协同,协助,互相配合,主旨在协同步调。按照预定先后次序运行。 -
线程同步,指一个线程发出来某一个调用时,在没有得到结果之前,该调用不返回。同时其它线程为了保证数据的一致性,不能调用该功能。说白了就是线程在执行任务时有先后顺序,为了防止线程资源被抢占。“同步”的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间,信号间等等都需要信号同步机制。
-
处理同步互斥方式有哪些
- 信号量---------->进程
- 有名信号量---------->进程
- 无名信号量---------->线程
- 互斥锁---------->线程
- 读写锁---------->线程
同步互斥方式之有名信号量
- 有名信号量的特点
有名信号量与信号量非常相似,但是信号量的值只能是0/1,而有名信号量可以是0~+∞
信号量使用空间+数据来处理互斥,而有名信号量只使用数据来处理 - 有名信号量的函数接口
- 创建并打开一个有名信号量------->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必须要填
- 有名信号量的P操作
- P操作:资源数-1操作------->sem_wait()
#include <semaphore.h>
int sem_wait(sem_t *sem); //参数:有名信号量的地址
// 返回值:成功0------>资源-1,失败-1
- 有名信号量的V操作
- V操作:资源数+1操作------->sem_post()
#include <semaphore.h>
int sem_post(sem_t *sem); //参数:有名信号量的地址
// 返回值:成功0------>资源+1,失败-1
Link with -pthread.
- 关闭有名信号量--------sem_close()
#include <semaphore.h>
int sem_close(sem_t *sem);//参数:有名信号量的地址
// 返回值:成功0,失败-1
Link with -pthread.
- 删除有名信号量--------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;
}
同步互斥方式之无名信号量
- 什么是无名信号量
一般作用于线程之间的互斥,由于是无名信号量,所以是没有名字的,不能用sem_open()打开 - 无名信号量的函数接口
- 定义一个无名信号量
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
- 无名信号量的V操作
- V操作:资源数+1操作------->sem_post()
#include <semaphore.h>
int sem_post(sem_t *sem); //参数:无名信号量的地址
// 返回值:成功0------>资源+1,失败-1
Link with -pthread.
- 销毁无名信号量--------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是没有缓冲区的
同步互斥方式之互斥锁
- 什么是互斥锁
- 互斥锁是专门用于处理线程互斥的一种方式,它有两种状态:上锁状态/解锁状态。如果互斥锁处于上锁状态,那么再上锁会阻塞,知道这把锁解开之后,才能上锁。解锁状态依然继续解锁,不会阻塞
- 注意
- 同一时刻,只有一个线程持有该锁
- 当线程A对某一全局变量加锁访问,线程B在访问前尝试加锁,会拿不到锁,线程B阻塞。一个线程C不去加锁,直接去访问该全局变量,依然可以访问,但会出现数据混乱
- 所以,互斥锁实际上给操作系统提供了一把“建议锁“(又称“协同锁”),建议程序中有多线程访问共享资源的使用该机制。但是没有强制限定。因此即使有了mutex锁,如果有线程不按规则来访问数据,依然会造成数据踩踏
- 关于线程互斥锁函数接口
- 定义互斥锁变量----->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错误码
-
lock和unlock
lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其它线程解锁为止。unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级,调度。默认是谁先被阻塞,谁先被唤醒
例如:P1,P2,P3,P4使用一把mutex锁。P1加锁成功,其他线程都阻塞,直到P1解锁。P1解锁后,P2,P3,P4均被唤醒,并自动
再次尝试加锁。
可以理解为:mutex锁init成功初值为1。lock功能是将mutex--。而unlock则将mutex++; -
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;
}
死锁
- 线程试图对同一互斥量加锁两次
- 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁