网站备案 写共享可以吗,山东省建设从业人员管理系统入口,莆田seo培训,哪里有免费建设网站Linux——线程条件变量#xff08;同步#xff09;-CSDN博客 文章目录 目录 文章目录 前言 一、信号量是什么#xff1f; 二、信号量 1、主要类型 2、操作 3、应用场景 三、信号量函数 1、sem_init 函数 2、sem_wait 函数 3、sem_post 函数 4、sem_destroy 函数 … Linux——线程条件变量同步-CSDN博客 文章目录 目录 文章目录 前言 一、信号量是什么 二、信号量 1、主要类型 2、操作 3、应用场景 三、信号量函数 1、sem_init 函数 2、sem_wait 函数 3、sem_post 函数 4、sem_destroy 函数 5、sem_getvalue 函数 6、sem_trywait 函数 7、sem_timedwait 函数 四、环形队列 1、定义与原理 2、操作 五、线程池 基本原理 主要功能 实现方式 六、基于环形队列的消费者模型 1、main函数 2、RingQueue.hpp 3、Task.hpp 编辑 前言
信号量Semaphore是一种用于多线程或多进程环境下实现同步和互斥的机制。 一、信号量是什么
信号量本质上是一个计数器用于控制对共享资源的访问。它的值表示当前可用的资源数量。当一个线程或进程想要访问某个共享资源时它需要先检查信号量的值。如果信号量的值大于 0则表示有可用资源该线程或进程可以获取资源并将信号量的值减 1如果信号量的值为 0则表示没有可用资源该线程或进程需要等待直到其他线程或进程释放资源使信号量的值大于 0。
二、信号量 1、主要类型 二进制信号量也称为互斥信号量它的值只能是 0 或 1。主要用于实现互斥访问确保在任何时刻只有一个线程或进程能够访问共享资源就像一个房间只有一把钥匙谁拿到钥匙谁才能进入房间使用里面的资源使用完后把钥匙放回其他人才有机会拿到钥匙进入。计数信号量其值可以是任意非负整数用于控制同时访问共享资源的线程或进程数量。比如有一个停车场有 10 个停车位就可以用一个初始值为 10 的计数信号量来表示每有一辆车进入停车场信号量的值就减 1当信号量的值为 0 时表示停车场已满后续车辆需要等待。
2、操作
P 操作也称为 wait 操作或 down 操作。当一个进程或线程执行 P 操作时它会检查信号量的值。如果信号量的值大于 0则将信号量的值减 1然后进程或线程可以继续执行如果信号量的值为 0则进程或线程会被阻塞放入等待队列直到信号量的值大于 0。V 操作也称为 signal 操作或 up 操作。当一个进程或线程执行 V 操作时它会将信号量的值加 1。如果此时有其他进程或线程正在等待该信号量即信号量的值为 0 且有进程在等待队列中则系统会从等待队列中唤醒一个进程或线程使其能够执行 P 操作并获取资源。
3、应用场景
资源管理可以用于管理系统中的各种资源如内存、文件、网络连接等。通过信号量可以确保资源的合理分配和使用避免资源冲突和过度使用。进程同步在多个进程或线程协同工作的场景中信号量可以用于实现进程之间的同步。例如一个进程需要等待另一个进程完成某个任务后才能继续执行就可以使用信号量来实现这种等待和唤醒机制。生产者 - 消费者问题是信号量应用的经典场景。生产者进程生产数据并将其放入缓冲区消费者进程从缓冲区中取出数据进行消费。通过信号量可以控制生产者和消费者的行为确保缓冲区不会被过度写入或读取。
三、信号量函数
1、sem_init 函数
功能用于初始化一个信号量。原型int sem_init(sem_t *sem, int pshared, unsigned int value);参数sem是指向要初始化的信号量的指针pshared指定信号量是否在进程间共享0 表示仅在线程间共享非 0 表示在进程间共享value是信号量的初始值。返回值成功时返回 0失败时返回 - 1并设置errno以指示错误原因。
2、sem_wait 函数
功能对信号量执行 P 操作即等待信号量变为可用。原型int sem_wait(sem_t *sem);参数sem是指向要操作的信号量的指针。返回值成功时返回 0若信号量的值为 0则线程会阻塞直到信号量可用失败时返回 - 1并设置errno。
3、sem_post 函数
功能对信号量执行 V 操作释放信号量使信号量的值加 1。原型int sem_post(sem_t *sem);参数sem是指向要操作的信号量的指针。返回值成功时返回 0失败时返回 - 1并设置errno。
4、sem_destroy 函数
功能销毁一个信号量释放相关资源。原型int sem_destroy(sem_t *sem);参数sem是指向要销毁的信号量的指针。返回值成功时返回 0失败时返回 - 1并设置errno。 5、sem_getvalue 函数
功能获取信号量的当前值。原型int sem_getvalue(sem_t *sem, int *sval);参数sem是指向要查询的信号量的指针sval是一个整数指针用于存储信号量的当前值。返回值成功时返回 0并将信号量的当前值存储在sval指向的位置失败时返回 - 1并设置errno以指示错误原因。
6、sem_trywait 函数
功能尝试对信号量执行 P 操作但不会阻塞线程。如果信号量的值大于 0则将信号量的值减 1 并立即返回如果信号量的值为 0则立即返回错误而不会阻塞线程。原型int sem_trywait(sem_t *sem);参数sem是指向要操作的信号量的指针。返回值成功时返回 0此时表示成功获取信号量并将其值减 1如果信号量的值为 0无法获取信号量则返回 - 1并将errno设置为EAGAIN。
7、sem_timedwait 函数
功能对信号量执行 P 操作但会设置一个超时时间。如果在超时时间内信号量变为可用则获取信号量并返回如果超时时间已过信号量仍不可用则返回错误。原型int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);参数sem是指向要操作的信号量的指针abs_timeout是一个指向struct timespec结构体的指针用于指定绝对超时时间。返回值成功时返回 0若在超时时间内未获取到信号量则返回 - 1并将errno设置为ETIMEDOUT。
四、环形队列
1、定义与原理
环形队列是一种基于队列的数据结构它将队列的首尾相连形成一个环形的存储空间。与普通队列不同环形队列可以更有效地利用存储空间避免了普通队列在元素出队后出现的前端空闲空间无法利用的问题。它通过使用两个指针即队头指针front和队尾指针rear来管理队列中的元素。当队尾指针到达队列的末尾时它会重新回到队列的开头继续存储新元素从而实现了循环利用空间的功能。
2、操作
初始化创建一个指定大小的数组来存储队列元素并将队头指针和队尾指针都初始化为 0表示队列为空。入队操作当要将一个新元素插入到环形队列中时首先检查队列是否已满。如果未满将新元素存储在队尾指针所指向的位置然后将队尾指针向后移动一位。如果队尾指针已经到达数组的末尾则将其重新设置为数组的开头位置。出队操作从环形队列中删除元素时首先检查队列是否为空。如果不为空取出队头指针所指向的元素然后将队头指针向后移动一位。同样如果队头指针到达数组的末尾也需要将其重新设置为数组的开头位置。判断队列空满 一般采用牺牲一个存储空间的方法来区分队列空和满的情况即当(rear 1) % maxSize front时认为队列已满其中maxSize是队列的最大容量当front rear时认为队列是空的。也可以使用一个计数器来记录队列中元素的个数当计数器的值为 0 时表示队列为空当计数器的值等于maxSize时表示队列已满。
五、线程池
线程池是一种多线程处理形式它将多个线程预先创建并放入一个池中以方便对线程进行管理和重复利用从而提高系统性能和资源利用率。以下是关于线程池的详细介绍
基本原理
线程创建与管理线程池在初始化时会创建一定数量的线程并将它们放入线程池中。这些线程在创建后不会立即执行具体任务而是处于等待状态等待接收任务并执行。任务队列线程池通常会维护一个任务队列用于存储待执行的任务。当有新任务到来时会将任务添加到任务队列中。线程池中的线程会不断从任务队列中获取任务并执行相应的操作。线程复用线程执行完一个任务后不会立即销毁而是返回到线程池中继续等待下一个任务。这样可以避免频繁地创建和销毁线程减少了线程创建和销毁所带来的开销提高了系统的性能和响应速度。
主要功能
提高资源利用率通过复用线程避免了因频繁创建和销毁线程而带来的资源浪费尤其是在处理大量短时间任务时能显著提高系统资源的利用率。控制并发度可以限制同时执行的线程数量避免过多线程同时运行导致系统资源过度消耗从而保证系统的稳定性和响应能力。简化线程管理将线程的创建、调度和管理等工作封装在一个线程池中使得开发者无需直接管理大量的线程降低了多线程编程的复杂性提高了代码的可维护性和可读性。
实现方式
线程池的组成部分 线程集合存储线程池中的所有线程一般使用线程数组或线程列表来实现。任务队列用于存放待执行的任务通常使用队列数据结构如阻塞队列来实现。当任务队列满时新任务可能会被阻塞或根据特定的策略进行处理。线程池管理模块负责线程池的初始化、线程的创建与销毁、任务的分配与调度等管理工作。它根据任务队列的状态和线程池的配置参数决定是否需要创建新的线程或回收空闲线程。 工作流程 任务提交用户将任务提交到线程池任务会被放入任务队列中。任务分配线程池中的线程会不断从任务队列中获取任务。当线程获取到任务后就开始执行任务。线程管理线程池管理模块会监控线程的状态当线程执行完任务后会将其重新放回线程池中使其可以继续执行下一个任务。如果线程池中的线程数量超过了最大线程数或者有空闲线程超过了一定的空闲时间线程池管理模块会负责销毁这些线程以释放资源。
六、基于环形队列的消费者模型 1、main函数
#include iostream
#include pthread.h
#include unistd.h
#include ctime
#include RingQueue.hpp
#include Task.hppusing namespace std;struct ThreadData
{RingQueueTask *rq;std::string threadname;
};void *Productor(void *args)
{// sleep(3);ThreadData *td static_castThreadData*(args);RingQueueTask *rq td-rq;std::string name td-threadname;int len opers.size();while (true){// 1. 获取数据int data1 rand() % 10 1;usleep(10);int data2 rand() % 10;char op opers[rand() % len];Task t(data1, data2, op);// 2. 生产数据rq-Push(t);cout Productor task done, task is : t.GetTask() who: name endl;sleep(1);}return nullptr;
}void *Consumer(void *args)
{ThreadData *td static_castThreadData*(args);RingQueueTask *rq td-rq;std::string name td-threadname;while (true){// 1. 消费数据Task t;rq-Pop(t);// 2. 处理数据t();cout Consumer get task, task is : t.GetTask() who: name result: t.GetResult() endl;// sleep(1);}return nullptr;
}int main()
{srand(time(nullptr) ^ getpid());RingQueueTask *rq new RingQueueTask(50);pthread_t c[5], p[3];for (int i 0; i 1; i){ThreadData *td new ThreadData();td-rq rq;td-threadname Productor- std::to_string(i);pthread_create(p i, nullptr, Productor, td);}for (int i 0; i 1; i){ThreadData *td new ThreadData();td-rq rq;td-threadname Consumer- std::to_string(i);pthread_create(c i, nullptr, Consumer, td);}for (int i 0; i 1; i){pthread_join(p[i], nullptr);}for (int i 0; i 1; i){pthread_join(c[i], nullptr);}return 0;
}
2、RingQueue.hpp
#pragma once
#include iostream
#include vector
#include semaphore.h
#include pthread.hconst static int defaultcap 5;templateclass T
class RingQueue{
private:void P(sem_t sem){sem_wait(sem);}void V(sem_t sem){sem_post(sem);}void Lock(pthread_mutex_t mutex){pthread_mutex_lock(mutex);}void Unlock(pthread_mutex_t mutex){pthread_mutex_unlock(mutex);}
public:RingQueue(int cap defaultcap):ringqueue_(cap), cap_(cap), c_step_(0), p_step_(0){sem_init(cdata_sem_, 0, 0);sem_init(pspace_sem_, 0, cap);pthread_mutex_init(c_mutex_, nullptr);pthread_mutex_init(p_mutex_, nullptr);}void Push(const T in) // 生产{P(pspace_sem_);Lock(p_mutex_); // ?ringqueue_[p_step_] in;// 位置后移维持环形特性p_step_;p_step_ % cap_;Unlock(p_mutex_); V(cdata_sem_);}void Pop(T *out) // 消费{P(cdata_sem_);Lock(c_mutex_); // ?*out ringqueue_[c_step_];// 位置后移维持环形特性c_step_;c_step_ % cap_;Unlock(c_mutex_); V(pspace_sem_);}~RingQueue(){sem_destroy(cdata_sem_);sem_destroy(pspace_sem_);pthread_mutex_destroy(c_mutex_);pthread_mutex_destroy(p_mutex_);}
private:std::vectorT ringqueue_;int cap_;int c_step_; // 消费者下标int p_step_; // 生产者下标sem_t cdata_sem_; // 消费者关注的数据资源sem_t pspace_sem_; // 生产者关注的空间资源pthread_mutex_t c_mutex_;pthread_mutex_t p_mutex_;
}; 3、Task.hpp
#pragma once
#include iostream
#include stringstd::string opers-*/%;enum{DivZero1,ModZero,Unknown
};class Task
{
public:Task(){}Task(int x, int y, char op) : data1_(x), data2_(y), oper_(op), result_(0), exitcode_(0){}void run(){switch (oper_){case :result_ data1_ data2_;break;case -:result_ data1_ - data2_;break;case *:result_ data1_ * data2_;break;case /:{if(data2_ 0) exitcode_ DivZero;else result_ data1_ / data2_;}break;case %:{if(data2_ 0) exitcode_ ModZero;else result_ data1_ % data2_;} break;default:exitcode_ Unknown;break;}}void operator ()(){run();}std::string GetResult(){std::string r std::to_string(data1_);r oper_;r std::to_string(data2_);r ;r std::to_string(result_);r [code: ;r std::to_string(exitcode_);r ];return r;}std::string GetTask(){std::string r std::to_string(data1_);r oper_;r std::to_string(data2_);r ?;return r;}~Task(){}private:int data1_;int data2_;char oper_;int result_;int exitcode_;
};