目录
同步与互斥的概述
互斥
是指散布在不同任务之间的若干程序片断,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这个程序片段后才可以运行。最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。
同步
是指散布在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运
行过程中协同步调,按预定的先后次序运行。
互斥锁(互斥量)
- 在访问共享资源后临界区域前,对互斥锁进行加锁。
- 在访问完成后释放互斥锁导上的锁。
- 对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。
类型
pthread_mutex_t
初始化
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict 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 错误码
restrict,C语言中的一种类型限定符(Type Qualifiers),用于告诉编译器,对象已经被指针所引用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。
申请上锁
#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错误码
销毁锁
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非 0 错误码
组合拳
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex;
//将字符串按照字符一个一个输出(1s输出一次)
void myprint(char *str)
{
while(*str != '\0'){
putchar(*str++);
fflush(stdout);
sleep(1);
}
printf("\n");
}
void *my_pthread_fun1(void * arg){
pthread_mutex_lock(&mutex);
myprint("hello");
pthread_mutex_unlock(&mutex);
}
void *my_pthread_fun(void * arg){
//pthread_mutex_lock(&mutex);
int ret;
while(1){
ret=pthread_mutex_trylock(&mutex);
if(ret==0)
break;
}
myprint("world");
pthread_mutex_unlock(&mutex);
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_t tid,tid1;
//任务1
pthread_create(&tid,NULL,my_pthread_fun,NULL);
//任务2
pthread_create(&tid1,NULL,my_pthread_fun1,NULL);
pthread_join(tid,NULL);
pthread_join(tid1,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
利用标志位实现互斥
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
int flag=0; //锁开 flag=1锁关
//将字符串按照字符一个一个输出(1s输出一次)
void myprint(char *str)
{
while(*str != '\0'){
putchar(*str++);
fflush(stdout);
sleep(1);
}
printf("\n");
}
void *my_pthread_fun1(void * arg){
while(1){
if(flag==0)
break;
}
flag++;
myprint("hello");
flag=0;
}
void *my_pthread_fun(void * arg){
while(1){
if(flag==0)
break;
}
flag++;
myprint("world");
flag=0;
}
int main()
{
pthread_t tid,tid1;
//任务1
pthread_create(&tid,NULL,my_pthread_fun,NULL);
//任务2
pthread_create(&tid1,NULL,my_pthread_fun1,NULL);
pthread_join(tid,NULL);
pthread_join(tid1,NULL);
return 0;
}
死锁
读写锁
解决
互斥且提高效率(读共享,写互斥)
特点
- 如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。
- 如果有其它线程写数据,则其它线程都不允许读、写操作
规则
- 如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁。
- 如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁
初始化
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict 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_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 <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
功能:
用于销毁一个读写锁,并释放所有相关联的资源(所谓的所有指的是由 pthread_rwlock_init() 自动申请的资源) 。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码
信号量
功能
实现互斥,实现同步
分类
无名信号量
有名信号量
本质
非负整数
支持的操作
pv操作,p给信号量-1,v给信号量+1
实现互斥
信号量如果<1,进行p操作会阻塞
初始化一个信号量,默认值=1,在要实现互斥的任务中添加p操作,执行线程任务,然后再执行v操作。
实现同步
实现多少个任务同步就初始化多少个信号量,初始值是第一个要执行的任务中信号量=1,其余全部=0,在所有任务中给自己的信号量p操作,然后执行线程任务,然后给下一个要执行任务中信号量进行v操作
无名信号量的使用
初始化
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value)
功能:
创建一个信号量并初始化它的值。一个无名信号量在被使用前必须先初始化。
参数:
sem:信号量的地址
pshared:等于 0,信号量在线程间共享(常用);不等于0,信号量在进程间共享。
value:信号量的初始值
返回值:
成功:0
失败: - 1
信号量减一
int sem_wait(sem_t *sem);
功能: 将信号量减一,如果信号量的值为0 则阻塞,大于0可以减一
参数:信号量的地址
返回值:成功返回0 失败返回-1
尝试对信号量减一
int sem_trywait(sem_t *sem);
功能: 尝试将信号量减一,如果信号量的值为0 不阻塞,立即返回 ,大于0可以减一
参数:信号量的地址
返回值:成功返回0 失败返回-1
信号量加一
int sem_post(sem_t *sem);
功能:将信号量加一
参数:信号量的地址
返回值:成功返回0 失败返回-1
销毁信号量
int sem_destroy(sem_t *sem);
功能: 销毁信号量
参数: 信号量的地址
返回值:成功返回0 失败返回-1
有名信号量的使用
打开/创建
#include <fcntl.h>
#include <sys/stat.h>
#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 intvalue);
功能:
创建一个信号量。
参数:
name:信号量文件名。
flags: sem_open 函数的行为标志。
mode:文件权限(可读、可写、可执行)的设置。
value :信号量初始值。
返回值:
成功返回信号量的地址,失败返回 SEM_FAILED
关闭
#include <semaphore.h>
int sem_close(sem_t *sem);
功能:
关闭有名信号量。
参数:
sem:指向信号量的指针。
返回值:成功返回 0,失败返回-1
删除
#include <semaphore.h>
int sem_unlink(const char *name);
功能:
删除信号量的文件。
参数:
name:信号量文件名。
返回值:
成功返回 0,失败返回-1