1.linux 9day
1.线程竞争
![01-打印机模型](I:\9 day\01-打印机模型.png)
2.同步和互斥
互斥
同一时刻只能一个进程或线程使用 多个进程或线程不能同时使用
同步:是指散步在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。
生产者消费者
显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。
互斥:无次序
同步:有次序
3.互斥锁函数介绍
1.互斥锁介绍
而在线程里也有这么一把锁:互斥锁(mutex),也叫互斥量,互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即加锁( lock )和解锁( unlock )。
1)在访问共享资源后临界区域前,对互斥锁进行加锁。
2)在访问完成后释放互斥锁导上的锁。
3)对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。
互斥锁的数据类型是: pthread_mutex_t。
2.初始化互斥锁 pthread_mutex_init
初始化互斥锁:
#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),用于告诉编译器,对象已经被指针所引用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。
3.互斥锁销毁 pthread_mutex_destroy
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非 0 错误码
4.上锁函数 pthread_mutex_lock
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
对互斥锁上锁,若互斥锁已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非 0 错误码
int pthread_mutex_trylock(pthread_mutex_t *mutex);
调用该函数时,若互斥锁未加锁,则上锁,返回 0;
若互斥锁已加锁,则函数直接返回失败,即 EBUSY。
4.互斥锁解锁 pthread_mutex_unlock
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
对指定的互斥锁解锁。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非0错误码
5.互斥锁案例
对一变量写入 理想20w ->但会被写入覆盖导致数据丢失 故此加锁
int ic = 0;
pthread_mutex_t mutex;//互斥锁
void* thread_fun1(void* arg)
{
for (int i = 0; i < 100000; i ++ )
{
pthread_mutex_lock(&mutex);//加锁
ic++;
pthread_mutex_unlock(&mutex);//解锁
}
}
void* thread_fun2(void* arg)
{
for (int i = 0; i < 100000; i++)
{
pthread_mutex_lock(&mutex);//加锁
ic++;
pthread_mutex_unlock(&mutex);//解锁
}
}
int main(void)
{
pthread_t tid1;
pthread_t tid2;
int ret = -1;
ret=pthread_mutex_init(&mutex,NULL);///创建锁
ret = pthread_create(&tid1, NULL, thread_fun1, NULL);
ret = pthread_create(&tid2, NULL, thread_fun2, NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
//创建线程完 等待线程跑完
pthread_mutex_destroy(&mutex);
printf("全局变量%d\n", ic);
}
4.死锁 互斥锁概念
什么是死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁原因
![04-死锁](I:\9 day\04-死锁.png)
竞争不可抢占资源引起死锁
竞争可消耗资源引起死锁
进程推进顺序不当引起死锁
死锁的必要条件
竞争资源,另一方需要等待资源
处理死锁
破坏死锁的循环
死锁代码演示
![死锁](I:\9 day\死锁.png)
5.读写锁
![06-读写锁](I:\9 day\06-读写锁.png)
在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用。为了满足当前能够允许多个读出,但只允许一个写入的需求,线程提供了读写锁来实现。
1.读写锁特点
1)如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。
2)如果有其它线程写数据,则其它线程都不允许读、写操作。
读写锁分为读锁和写锁,规则如下:
1)如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁。
2)如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁。
POSIX 定义的读写锁的数据类型是: pthread_rwlock_t。
2.读写锁函数
1.初始化读写锁 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;
这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_rwlock_init() 来完成动态初始化,不同之处在于PTHREAD_RWLOCK_INITIALIZER 宏不进行错误检查。
返回值:
成功:0,读写锁的状态将成为已初始化和已解锁。
失败:非 0 错误码。
2.销毁读写锁 pthread_rwlock_destroy
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
功能:
用于销毁一个读写锁,并释放所有相关联的资源(所谓的所有指的是由 pthread_rwlock_init() 自动申请的资源) 。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码
3.加读锁 pthread_rwlock_rdlock
#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);
用于尝试以非阻塞的方式来在读写锁上获取读锁。
如果有任何的写者持有该锁或有写者阻塞在该读写锁上,则立即失败返回。
4.加写锁 pthread_rwlock_wrlock
#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
功能:
在读写锁上获取写锁(写锁定)。
如果没有写者持有该锁,并且没有写者读者持有该锁,则调用线程会获取写锁。
如果调用线程未获取写锁,则它将阻塞直到它获取了该锁。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
用于尝试以非阻塞的方式来在读写锁上获取写锁。
如果有任何的读者或写者持有该锁,则立即失败返回。
4.解锁 pthread_rwlock_unlock
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
功能:
无论是读锁或写锁,都可以通过此函数解锁。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码
5.读写锁示例
读写案例
//写fifo1 读fifo2
int ic = 0;
pthread_rwlock_t rwlock;
void* fun_read(void* arg)
{
while(1)
{
//读取ic值
int index = 0;
sleep(1);
pthread_rwlock_rdlock(&rwlock);
printf("线程%d 读取num的值%d \n", index, ic);
//解锁
pthread_rwlock_unlock(&rwlock);
}
}
void* fun_write(void* arg)
{
//读取ic值
int index = 1;
while(1)
{
pthread_rwlock_wrlock(&rwlock);
ic++;
printf("线程%d 写num的值%d \n", index, ic);
sleep(1);
//解锁
pthread_rwlock_unlock(&rwlock);
}
}
int main(void)
{
pthread_t tid[8];
int i = 0;
int ret = -1;
//初始化读锁
ret = pthread_rwlock_init(&rwlock, NULL);
for (i = 0; i < 8; i++)
{
if (i < 5)
{
pthread_create(&tid[i], NULL, fun_read, NULL);
}
else
{
pthread_create(&tid[i], NULL, fun_write,NULL);
}
}
//回收资源
for (i = 0; i < 8; i++)
{
pthread_join(tid[i], NULL);
}
return 0;
}
创建八个线程 5读 3写
6.条件变量控制
1.条件变量概念
与互斥锁不同,条件变量是用来等待而不是用来上锁的,条件变量本身不是锁!
条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
条件变量的两个动作:
- 条件不满, 阻塞线程
- 当条件满足, 通知阻塞的线程开始工作
条件变量的类型: pthread_cond_t。
2.条件变量函数
1.初始化条件变量
#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;
返回值:
成功:0
失败:非0错误号
2.销毁条件变量
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
功能:
销毁一个条件变量
参数:
cond:指向要初始化的条件变量指针
返回值:
成功:0
失败:非0错误号
3.阻塞等待条件变量
#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_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct
.*restrict abstime);
功能:
限时等待一个条件变量
参数:
cond:指向要初始化的条件变量指针
mutex:互斥锁
abstime:绝对时间
返回值:
成功:0
失败:非0错误号
abstime补充说明:
struct timespec {
time_t tv_sec; /* seconds */ // 秒
long tv_nsec; /* nanosecondes*/ // 纳秒
}
time_t cur = time(NULL); //获取当前时间。
struct timespec t; //定义timespec 结构体变量t
t.tv_sec = cur + 1; // 定时1秒
pthread_cond_timedwait(&cond, &t);
4.唤醒条件变量线程
#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错误号
条件变量示例
#define SIZE 128
int ic = 0;
pthread_cond_t cond;
pthread_mutex_t mutex;
void* thread_fun1(void* arg)
{
while (1)
{
pthread_mutex_lock(&mutex);
ic = 1;
pthread_mutex_unlock(&mutex);
printf("条件已经改变\n");
//唤醒因为条件阻塞的线程
pthread_cond_signal(&cond);
sleep(2);
}
}
void* thread_fun2(void* arg)
{
while (1)
{
pthread_mutex_lock(&mutex);
if (0 == ic)
{
//等待条件
pthread_cond_wait(&cond, &mutex);
//如果cpu先划分线程2 这个等待条件内部会自动解锁一次
//带函数退出再加锁
}
printf("线程2执行\n");
//唤醒因为条件阻塞的线程
ic = 0;
pthread_mutex_unlock(&mutex);
sleep(2);
}
}
int main(void)
{
pthread_t tid1;
pthread_t tid2;
int ret = -1;
//初始化锁
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
ret = pthread_create(&tid1, NULL, thread_fun1, NULL);
ret = pthread_create(&tid2, NULL, thread_fun2, NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
}
生产者消费者模型
ps:两个线程 一个模拟生产者行为 一个模拟消费者行为 两个线程同时操作一个共享资源,生产向其中添加产品,消费者从中消费掉产品
![生产者消费者模型](I:\9 day\生产者消费者模型.jpeg)
int ic = 0;
pthread_cond_t cond;
pthread_mutex_t mutex;
// 永远指向链表头部的指针
typedef struct node
{
int data;
struct node* next;
}node_t;
node_t* head = NULL;
//生产者
void* thread_fun1(void* arg)
{
while (1)
{
node_t* new = malloc(sizeof(node_t));
if (NULL == new)
{
printf("malloc");
break;
}
memset(new, 0, sizeof(node_t));
pthread_mutex_lock(&mutex);
new->data = rand() % 100 + 1;
new->next = NULL;
//头插法
new->next = head;
head = new;
pthread_mutex_unlock(&mutex);//解锁
pthread_cond_signal(&cond);//通知消费者线程
sleep(rand() % 3);
}
return NULL;
}
//消费者
void* thread_fun2(void* arg)
{
while (1)
{
pthread_mutex_lock(&mutex);
if (head = NULL);
{
//阻塞
pthread_cond_wait(&cond, &mutex);
}
//删除节点
node_t* pdel = head;
head = head->next;
printf("数据去吃 % d\n", pdel->data);
free(pdel);
pthread_mutex_unlock(&mutex);
}
}
int main(void)
{
pthread_t tid1;
pthread_t tid2;
int ret = -1;
//初始化锁
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
ret = pthread_create(&tid1, NULL, thread_fun1, NULL);
ret = pthread_create(&tid2, NULL, thread_fun2, NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
}
7.信号量
1.信号量概述
信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞。
PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。
信号量数据类型为:sem_t。
信号量用于互斥:
![信号量互斥](I:\9 day\信号量互斥.png)
信号量同步
![信号量同步](I:\9 day\信号量同步.png)
2.信号量函数
1.初始化信号量:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:
创建一个信号量并初始化它的值。一个无名信号量在被使用前必须先初始化。
参数:
sem:信号量的地址。
pshared:等于 0,信号量在线程间共享(常用);不等于0,信号量在进程间共享。
value:信号量的初始值。
返回值:
成功:0
失败: - 1
2.销毁信号量:
#include <semaphore.h>
int sem_destroy(sem_t *sem);
功能:
删除 sem 标识的信号量。
参数:
sem:信号量地址。
返回值:
成功:0
失败: - 1
3.信号量p操作
#include <semaphore.h>
int sem_wait(sem_t *sem);
功能:
将信号量的值减 1。操作前,先检查信号量(sem)的值是否为 0,若信号量为 0,此函数会阻塞,直到信号量大于 0 时才进行减 1 操作。
参数:
sem:信号量的地址。
返回值:
成功:0
失败: - 1
int sem_trywait(sem_t *sem);
以非阻塞的方式来对信号量进行减 1 操作。
若操作前,信号量的值等于 0,则对信号量的操作失败,函数立即返回。
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
限时尝试将信号量的值减 1
abs_timeout:绝对时间
abs_timeout补充说明:
struct timespec {
time_t tv_sec; /* seconds */ // 秒
long tv_nsec; /* nanosecondes*/ // 纳秒
}
time_t cur = time(NULL); //获取当前时间。
struct timespec t; //定义timespec 结构体变量t
t.tv_sec = cur + 1; // 定时1秒
sem_timedwait(&cond, &t);
4.信号量v操作
#include <semaphore.h>
int sem_post(sem_t *sem);
功能:
将信号量的值加 1 并发出信号唤醒等待线程(sem_wait())。
参数:
sem:信号量的地址。
返回值:
成功:0
失败:-1
5.信号量获取
#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);
功能:
获取 sem 标识的信号量的值,保存在 sval 中。
参数:
sem:信号量地址。
sval:保存信号量值的地址。
返回值:
成功:0
失败:-1
信号量案例
sem_t sem; //信号量
void printer(char *str)
{
sem_wait(&sem);//减一
while (*str)
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");
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(void)
{
pthread_t tid1, tid2;
sem_init(&sem, 0, 1); //初始化信号量,初始值为 1
//创建 2 个线程
pthread_create(&tid1, NULL, thread_fun1, NULL);
pthread_create(&tid2, NULL, thread_fun2, NULL);
//等待线程结束,回收其资源
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
sem_destroy(&sem); //销毁信号量
return 0;
}
1.信号量的生产者消费者模型
#define SIZE 128
int ic = 0;
sem_t sem_producer;//容器量
sem_t semmsl;//可以卖的数量
// 永远指向链表头部的指针
typedef struct node
{
int data;
struct node* next;
}node_t;
node_t* head = NULL;
//生产者
void* thread_fun1(void* arg)
{
while (1)
{
//申请1个容器 sem_wait
sem_wait(&sem_producer);
node_t* new = malloc(sizeof(node_t));
if (NULL == new)
{
printf("malloc");
break;
}
memset(new, 0, sizeof(node_t));
new->data = rand() % 100 + 1;
new->next = NULL;
//头插法
new->next = head;
head = new;
sem_post(&semmsl);//通知消费
sleep(rand() % 3);
}
return NULL;
}
//消费者
void* thread_fun2(void* arg)
{
while (1)
{
sem_wait(&semmsl);//消费
//删除节点
node_t* pdel = head;
head = head->next;
printf("数据去吃 % d\n", pdel->data);
free(pdel);
sem_post(&sem_producer);//容器+1
}
}
int main(void)
{
pthread_t tid1;
pthread_t tid2;
int ret = -1;
//容器
sem_init(&sem_producer, 0, 4);
//可卖数量
sem_init(&semmsl, 0, 0);
ret = pthread_create(&tid1, NULL, thread_fun1, NULL);
ret = pthread_create(&tid2, NULL, thread_fun2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
}
标签:rwlock,linux9,互斥,死锁,mutex,pthread,sem,NULL
From: https://www.cnblogs.com/lzfyz/p/17568398.html