线程的互斥与同步

news/2024/12/3 7:48:27/

线程的加载

在内存当中我们知道还有一个关于共享区的概念,在这上面他有对库映射的虚拟地址,也有对创建的线程pthread做的管理。

我们所用的pthread_create()函数呢,其实也就是就是返回在共享区里创建的线程地址。而线程地址指向的首地址其实是线程需要第一次进入的一个函数的首地址。

而在源码里边他就设置了这些函数,所以规定了我们只能传入对应的函数参数,以及函数指针

互斥与同步

在有时不同线程会去访问进程里全局的资源,我们将这种会共同去访问的全局资源,叫做共享资源,也叫临界资源,操作临界资源的代码块我们又叫做临界区。
互斥:当线程要访问临界资源时就会相互竞争,这就叫做互斥。
同步:线程在访问临界资源时需要有序的进程访问,也就是排队访问,这就是同步。

所以对于线程访问临界资源我们是需要加锁处理的我们一般有两种锁

互斥锁

pthread_mutex_t

pthread_mutex是多线程编程中用来实现互斥的一种机制。它可以用来保护共享资源,确保在某一时刻只有一个线程可以访问该资源。

以下是pthread_mutex的几个常用函数的详细讲解:

  1. pthread_mutex_init
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)

该函数用来初始化一个互斥锁。需要传入一个pthread_mutex_t类型的指针作为参数,用来指向要初始化的互斥锁对象。还可以通过第二个参数attr来指定互斥锁的属性,如果不需要特定属性则可以传入NULL。

  1. pthread_mutex_lock
int pthread_mutex_lock(pthread_mutex_t *mutex)

该函数用来加锁一个互斥锁。需要传入一个pthread_mutex_t类型的指针作为参数,指向要加锁的互斥锁对象。如果互斥锁当前处于被锁定状态,则调用线程会被阻塞,直到互斥锁被解锁。

  1. pthread_mutex_trylock
int pthread_mutex_trylock(pthread_mutex_t *mutex)

该函数尝试对互斥锁进行加锁。与pthread_mutex_lock不同的是,如果互斥锁当前处于被锁定状态,则函数立即返回,不会阻塞线程。如果加锁成功,则返回0;否则,返回EBUSY。

  1. pthread_mutex_unlock
int pthread_mutex_unlock(pthread_mutex_t *mutex)

该函数解锁一个互斥锁。需要传入一个pthread_mutex_t类型的指针作为参数,指向要解锁的互斥锁对象。如果有其他线程正在等待这个互斥锁,则其中一个线程会被唤醒并获得锁。

  1. pthread_mutex_destroy
int pthread_mutex_destroy(pthread_mutex_t *mutex)

该函数销毁一个互斥锁。需要传入一个pthread_mutex_t类型的指针作为参数,指向要销毁的互斥锁对象。在程序退出前应该调用该函数来销毁互斥锁。

使用pthread_mutex的一般步骤如下:

  1. 定义一个pthread_mutex_t类型的变量作为互斥锁。

  2. 在需要对共享资源进行访问的代码块中,通过调用pthread_mutex_lock函数来加锁。

  3. 在使用完共享资源后,通过调用pthread_mutex_unlock函数来解锁。

  4. 在程序退出前,通过调用pthread_mutex_destroy函数来销毁互斥锁。

通过使用互斥锁,可以确保在某一时刻只有一个线程可以访问共享资源,从而避免竞争条件和数据的不一致性问题。

加锁部分源码:


根据这个

拿抢票来举例子

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
int ticket = 3000;
pthread_mutex_t mutex;void *route(void *arg)
{char *id = (char *)arg;while (1){pthread_mutex_lock(&mutex);if (ticket > 0){usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}}
}int main(void)
{pthread_t t1, t2, t3, t4;//pthread_mutex_init(&mutex, NULL);pthread_create(&t1, NULL, route, (void *)"thread 1");pthread_create(&t2, NULL, route, (void *)"thread 2");pthread_create(&t3, NULL, route, (void *)"thread 3");pthread_create(&t4, NULL, route, (void *)"thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);//pthread_mutex_destroy(&mutex);
}

我们抢票时,访问资源肯定肯定会去访问临界资源的,如果我们不加锁,抢票的时候就会有这样一个结果。
不加锁情况!


因为当票为1时肯定会有多个线程去抢这个票,如果不加保护,就会使这个票减减多次。出现这样的结果。
加锁情况


加上之后,他们就可以做同步了

条件变量

pthread_cond_t是一个条件变量类型,用于线程间的同步和通信。它是一种线程间的信号机制,可以用来实现线程的等待和唤醒操作。

pthread_cond_t的使用通常需要和pthread_mutex_t配合使用,用于实现互斥访问共享资源。

下面是pthread_cond_t的一般用法:

定义和初始化条件变量:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);

等待条件的线程调用pthread_cond_wait()函数来进入等待状态(释放已经持有的互斥锁,并等待条件变量的信号):

pthread_mutex_lock(&mutex);   // 先加锁
while (条件不满足) {pthread_cond_wait(&cond, &mutex);  // 等待条件变量的信号,并释放锁
}
pthread_mutex_unlock(&mutex);   // 条件满足,解锁

满足条件的线程调用pthread_cond_signal()或pthread_cond_broadcast()函数来发送信号,告知等待的线程条件已经满足:

pthread_mutex_lock(&mutex);   // 先加锁
// 修改条件、操作共享资源...
pthread_cond_signal(&cond);   // 发送信号,唤醒一个线程
// pthread_cond_broadcast(&cond);  // 发送信号,唤醒所有线程
pthread_mutex_unlock(&mutex);   // 解锁

注意:pthread_cond_signal()函数只是唤醒一个等待的线程,而pthread_cond_broadcast()函数则是唤醒所有等待的线程

基于阻塞队列的生产消费者模型

⽣产者消费者模式就是通过⼀个容器来解决⽣产者和消费者的强耦合问题。⽣产者和消费者彼此之间 不直接通讯,⽽通过阻塞队列来进⾏通讯,所以⽣产者⽣产完数据之后不⽤等待消费者处理,直接扔 给阻塞队列,消费者不找⽣产者要数据,⽽是直接从阻塞队列⾥取,阻塞队列就相当于⼀个缓冲区, 平衡了⽣产者和消费者的处理能⼒。这个阻塞队列就是⽤来给⽣产者和消费者解耦的。

这样生产者与消费就可以解耦。
这时我们只需要维护好三个原则!
3种关系生产者与生产者,1.生产者与生产者(互斥),2.消费者与消费者(互斥),3.消费者与生产者(互斥加同步)。
2个角色,生产者与消费者。
1个交易场所--基于阻塞队列或环形队列的生产场所
 

#include<iostream>
#include<pthread.h>
#include<queue>
#include<unistd.h>
using std::cout;
using std::endl;namespace zgw
{const int gcapa=4;template<class T>class BlockQueue{public:bool Full(){return _data.size()==_capacity;}bool Empty(){return _data.empty();}BlockQueue(int capacity=gcapa):_capacity(capacity){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_consumer,nullptr);pthread_cond_init(&_productor,nullptr);}void Push(const T& getdata){pthread_mutex_lock(&_mutex);while(Full()){_pwait++;pthread_cond_wait(&_productor,&_mutex);//如果满了就进入等待队列,等待中如果遇到signal,并且有锁才会重新申请到锁_pwait--;}_data.push(getdata);if(_cwait){pthread_cond_signal(&_consumer);}pthread_mutex_unlock(&_mutex);}void Get(T*getdata){pthread_mutex_lock(&_mutex);while(Empty()){_cwait++;pthread_cond_wait(&_consumer,&_mutex);_cwait--;}*getdata=_data.front();_data.pop();if(_pwait){pthread_cond_signal(&_productor);}pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_consumer);pthread_cond_destroy(&_productor);}private:int _pwait=0;int _cwait=0;int _capacity;std::queue<T> _data;pthread_mutex_t _mutex;pthread_cond_t _consumer;  //消费等待队列pthread_cond_t _productor;  //生产者等待队列};
}

POSIX信号量

POSIX信号量(POSIX semaphore)的使用方法如下:

包含头文件:

#include <semaphore.h>
  1. 定义信号量变量:
sem_t semaphore;
  1. 初始化信号量
sem_init(&semaphore, 0, initial_value);

其中,第一个参数是指向信号量变量的指针,第二个参数是用于指定信号量的作用范围(0表示线程间共享,非0表示进程间共享),第三个参数是信号量的初始值。

等待信号量(P操作):

sem_wait(&semaphore);

该函数会阻塞当前线程,直到信号量的值大于0。一旦信号量的值大于0,它会将信号量的值减1。

发送信号量(V操作):

sem_post(&semaphore);

该函数会将信号量的值加1。如果有其他线程正因为等待信号量而被阻塞,它会唤醒其中一个线程

销毁信号量

sem_destroy(&semaphore);

该函数会释放信号量变量占用的资源。在销毁信号量之前,必须确保没有线程在对该信号量进行等待操作。
需要注意的是,对于信号量的操作应该在互斥锁的保护下进行,以确保操作的原子性和正确性。以上是POSIX信号量的基本使用方法,可以根据具体的应用场景和需求进行适当的调整和扩展。

基于环形队列的⽣产消费模型

已经将信号量做了封装

#pragma once#include "Cond.hpp"
//#include"Mutex.hpp"
#include <queue>
#include <vector>
#include "Sem.hpp"
using std::cout;
using std::endl;namespace zgw
{int gcapa = 4;template <class T>class Ringqueue{public:Ringqueue(int _capa = gcapa) : _sempro(_capa), _semcon(0), _capacity(_capa), _rqueue(_capa), _curpro(0), _curcon(0){}void Set(T *data){_sempro.P();{_mutexpro.Lock();_rqueue[_curpro++] = *data;_curpro %= _capacity;_mutexpro.Unlock();}_semcon.V();}void Get(T *data){_semcon.P();{_mutexcon.Lock();*data = _rqueue[_curcon++];_curcon %=_capacity;_mutexcon.Unlock();}_sempro.V();}~Ringqueue(){}private:int _curpro;int _curcon;std::vector<T> _rqueue;int _capacity;Sem _sempro;Sem _semcon;Mymutex _mutexcon;Mymutex _mutexpro;};
}zgw::Ringqueue<int> rq;

http://www.ppmy.cn/news/1551941.html

相关文章

学习笔记047——Spring 框架核心源码笔记

文章目录 1、Spring 框架核心源码2、IoC 核心思想3、Spring IoC 的使用4、IoC 基于注解的执行原理 1、Spring 框架核心源码 1、使用 Spring 框架 2、反射机制 IoC 控制反转 Inverse of Control 创建对象的权限&#xff0c;Java 程序中需要用到的对象不再由程序员自己创建&am…

计算机网络--网络安全测试

问题 1 以下关于网络安全威胁发展的趋势的描述中错误的是___A_____。 答案&#xff1a; A云计算可以有效地防止网络攻击发生 B网络攻击、病毒与垃圾邮件是网络安全的三大公害 C网络攻击开始演变成某些国家或利益集团重要的政治、军事工具 D趋利性是当前网络攻击的主要特点 …

二分法篇——于上下边界的扭转压缩间,窥见正解辉映之光(2)

前言 上篇介绍了二分法的相关原理并结合具体题目进行讲解运用&#xff0c;本篇将加大难度&#xff0c;进一步强化对二分法的掌握。 一. 寻找峰值 1.1 题目链接&#xff1a;https://leetcode.cn/problems/find-peak-element/description/ 1.2 题目分析: 题目要求返回数组内…

关于BeanUtils.copyProperties是否能正常复制字段【详细版】

话不多说&#xff01;先总结&#xff1a; 1、字段相同&#xff0c;类型不同&#xff08;不复制&#xff0c;也不报错&#xff09; 2、子类父类 (1)子类传给父类&#xff08;可以正常复制&#xff09; (2)父类传给子类&#xff08;可以正常复制&#xff09; 3、子类父类&#x…

挑战用React封装100个组件【006】

项目地址 https://github.com/hismeyy/react-component-100 组件描述 组件适用于展示个人信息&#xff0c;别人还可以关注&#xff0c;发消息操作 样式展示 前置依赖 今天我们的这个挑战需要用用到了 react-icons 依赖&#xff0c;因此&#xff0c;我们需要先安装它。 # …

【07】MySQL中的DQL(数据查询语言)详解

文章目录 一、基础查询&#xff1a;SELECT语句1.1 查询指定列的数据1.2 查询所有列的数据1.3 查询去重数据 二、FROM 子句三、连接查询&#xff1a;JOIN 语句3.1 INNER JOIN3.2 LEFT JOIN&#xff08;或 LEFT OUTER JOIN&#xff09;3.3 RIGHT JOIN&#xff08;或 RIGHT OUTER …

重塑企业报修效率:报修进度查询功能深度解析与优化方向

在当下快节奏的现代企业运营环境中&#xff0c;设备设施的维护修理工作扮演着举足轻重的角色。无论是生产线上繁忙的机械部件&#xff0c;还是办公区不可或缺的电子装备&#xff0c;任何故障的出现都将直接影响企业的生产效率和日常运营。因此&#xff0c;构建一个高效且便捷的…

C/C++基础知识复习(32)

1) 什么是 C 中的函数对象&#xff1f;它有什么特点&#xff1f; 函数对象&#xff08;Function Object&#xff09; 是一个可以像函数一样调用的对象。换句话说&#xff0c;函数对象是重载了 operator() 运算符的类或结构体的实例。由于 C 中一切都是对象&#xff0c;函数对象…