系统编程day10-同步与互斥
1.同步与互斥
互斥:是指散布在不同任务之间的若干程序片段,当某个任务运行其中一个程序片段时,其他任务就不能运行他们之中的任一程序片段,只能等到该任务运行完成这个程序片段后才可以运行。
同步:是指散布在不同人物之间的若干程序片段,他们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定任务。最基本的任务场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如B任务的运行依赖于A任务产生的数据。
结合后面互斥锁去理解:
最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。
互斥:当多个进程或者线程需要使用同一个公共资源的时候,他们不可能同时使用(互斥),需要去抢,谁抢到就是谁的。
同步:当多个进程或者线程需要使用同一个公共资源的时候,他们不可能同时使用,但是不需要抢,按照约定好的顺序依次使用。
无论互斥还是同步,一个公共资源一次只能由一个进程或者线程使用。所以同步本质上也是一种特殊的互斥。
1.1互斥锁
线程里也有这么一把锁,互斥锁(mutex),也叫互斥量,互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即加锁(lock)和解锁(unlock)。
互斥锁的操作流程如下:
- 在访问共享资源后临界区域前,对互斥锁进行加锁。 谁加上锁,就归谁使用
- 在访问完成后释放互斥锁导上的锁。
- 对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。
注意:这里的上锁指的是拥有使用权,而不是把他关起来。
互斥锁的数据类型是: pthread_mutex_t。
1.2互斥锁的初始化
初始化互斥锁:pthread_mutex_init 函数
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutex_attr_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(&mutex ,NULL) 来完成动态初始化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER //用宏初始化
pthread_mutex_init(&mutex ,NULL) //用函数初始化,两个等价
返回值:
成功:0,成功申请的锁默认是打开的。
失败:非 0 错误码
1.3互斥锁的销毁
pthread_mutex_destroy函数
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非 0 错误码
1.4互斥锁的使用
上锁(lock):
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
对互斥锁上锁,若互斥锁已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非 0 错误码
解锁(unlock):
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
对互斥锁解锁
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非 0 错误码
1.5互斥锁的案例
不加锁的危害:
#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>void show_str(void *str);
void * myfun1(void *argv);
void * myfun2(void *argv);int main(int argc, char const *argv[])
{pthread_t pt1,pt2;pthread_create(&pt1,NULL,myfun1,"hello world");pthread_create(&pt2,NULL,myfun2,"xiazhoujian ");pthread_join(pt1,NULL);pthread_join(pt2,NULL);return 0;
}void show_str(void *str)
{char * p = (char *)str;int i =0;while (*p != '\0'){printf("%c",p[i]);fflush(stdout); //强制刷新输出缓冲区i++;sleep(1);}}void * myfun1(void *argv)
{show_str(argv);return NULL;
}void * myfun2(void *argv)
{show_str(argv);return NULL;}
输出是乱序的。
加锁之后:
#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
pthread_mutex_t mutex;void show_str(void *str);
void *myfun1(void *argv);
void *myfun2(void *argv);int main(int argc, char const *argv[])
{pthread_t pt1, pt2;pthread_mutex_init(&mutex, NULL); // 初始化锁pthread_create(&pt1, NULL, myfun1, "hello world");pthread_create(&pt2, NULL, myfun2, "xiazhoujian ");pthread_join(pt1, NULL);pthread_join(pt2, NULL);pthread_mutex_destroy(&mutex); // 销毁锁return 0;
}void show_str(void *str)
{char *p = (char *)str;int i = 0;while (*p != '\0'){printf("%c", p[i]);fflush(stdout); // 强制刷新输出缓冲区i++;sleep(1);}
}void *myfun1(void *argv)
{pthread_mutex_lock(&mutex); // 上锁show_str(argv);pthread_mutex_unlock(&mutex); // 解锁return NULL;
}void *myfun2(void *argv)
{pthread_mutex_lock(&mutex); // 上锁show_str(argv);pthread_mutex_unlock(&mutex); // 解锁return NULL;
}
注意:实现互斥只需要一把锁
2.死锁
2.1死锁情况
情况1:
线程A抢到资源上锁以后,突然由于某种原因消失,那么就一直未解锁,那么B就无法使用
情况2:
情况3:
解决死锁的方法:
1: 所有进程开始前,必须一次性地申请所需的所有资源,这样运行期间就不会再提出资源要求,破坏了请求条件,即使有一种资源不能满足需求,也不会给它分配正在空闲的资源,这样它就没有资源,就破坏了保持条件,从而预防死锁的发生。
2.允许一个进程只获得初期的资源就开始运行,然后再把运行完的资源释放出来。然后再请求新的资源。
3.条件变量
与互斥锁不同,条件变量是用来等待而不是用来上锁的,条件变量本身不是锁!条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。 条件变量的两个动作: 条件不满, 阻塞线程 当条件满足, 通知阻塞的线程开始工作。
条件变量的类型: pthread_cond_t;
3.1条件变量的API
初始化:
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
功能:
初始化一个条件变量
参数:
cond:指向要初始化的条件变量指针。
attr:条件变量属性,通常为默认值,传 NULL 即可
也可以使用静态初始化的方法:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;等价于:pthread_cond_init(&cond,NULL)
返回值:
成功: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 *restrict cond, pthread_mutex_t *restrict mutex);
功能:
阻塞等待一个条件变量
a) 阻塞等待条件变量 cond(参 1)满足
b) 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex); a)b) 两步为一个原子操作(一起执行,没有中间态)。
c) 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex_lock(&mutex);
参数:
cond:指向要初始化的条件变量指针
mutex:互斥锁
返回值:
成功:0
失败:非 0 错误号
唤醒所有:
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:
唤醒全部阻塞在条件变量上的线程
参数:
cond:指向要初始化的条件变量指针
返回值:
成功:0
失败:非 0 错误号
4.读写锁
特点:
读写锁的特点如下:
1)如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。
2)如果有其它线程写数据,则其它线程都不允许读、写操作。
规则如下:
1)如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁。
2)如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁。
4.1读写锁API:
读写锁的数据类型是: pthread_rwlock_t
初始化pthread_rwlock_init:
#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;等价于pthread_rwlock_init(&rwlock,NULL) 来完成动态初始化,不同之处在于 PTHREAD_RWLOCK_INITIALIZER 宏不进行错误检查。
返回值:
成功:0,读写锁的状态将成为已初始化和已解锁。
失败:非 0 错误码。
销毁destory:
#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 错误码
写锁:
#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
功能:
如果调用线程未获取写锁,则它将阻塞直到它获取了该锁。(因为别的线程写的时候,不能申请写锁,所以会阻塞)
参数:
rwlock:读写锁指针
返回值:
成功:0
失败:非 0 错误码
释放:
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
功能:
无论是读锁或写锁,都可以通过此函数解锁。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码
这里我们的系统编程就完结了,下周开始网络编程。
接上篇,为了庆祝粉丝过100,恭喜下面这个小伙伴喜提奶茶一杯。