Linux生产者消费模型

news2024/12/25 2:15:09

1.生产者消费者模型

1.1 为何要使用生产者消费者模型

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

1.2 生产者消费者模型优点

  • 解耦
  • 支持并发
  • 支持忙闲不均

 

 2.基于BlockingQueue的生产者消费者模型

2.1 BlockingQueue

     在多线程编程中阻塞队列 (Blocking Queue) 是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出( 以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)。

 2.2 C++ queue模拟阻塞队列的生产消费模型

#include <iostream>
#include <queue>
#include <stdlib.h>
#include <pthread.h>

#define NUM 8

class BlockQueue 
{
private:
	std::queue<int> q;
	int cap;
	pthread_mutex_t lock;
	pthread_cond_t full;
	pthread_cond_t empty;

private:
	void LockQueue()
	{
		pthread_mutex_lock(&lock);
	}
	void UnLockQueue()
	{
		pthread_mutex_unlock(&lock);
	}
	void ProductWait()
	{
		pthread_cond_wait(&full, &lock);
	}
	void ConsumeWait()
	{
		pthread_cond_wait(&empty, &lock);
	}
	void NotifyProduct()
	{
		pthread_cond_signal(&full);
	}
	void NotifyConsume()
	{
		pthread_cond_signal(&empty);
	}
	bool IsEmpty()
	{
		return (q.size() == 0 ? true : false);
	}
	bool IsFull()
	{
		return (q.size() == cap ? true : false);
	}

public:
	BlockQueue(int _cap = NUM) :cap(_cap)
	{
		pthread_mutex_init(&lock, NULL);
		pthread_cond_init(&full, NULL);
		pthread_cond_init(&empty, NULL);
	}
	void PushData(const int& data)
	{
		LockQueue();
		while (IsFull())
		{
			NotifyConsume();
			std::cout << "queue full, notify consume data, product stop." << std::endl;
			ProductWait();
		}
		q.push(data);
		// NotifyConsume();
		UnLockQueue();
	}
	void PopData(int& data)
	{
		LockQueue();
		while (IsEmpty())
		{
			NotifyProduct();
			std::cout << "queue empty, notify product data, consume stop." << std::endl;
			ConsumeWait();
		}
		data = q.front();
		q.pop();
		// NotifyProduct();
		UnLockQueue();
	}
	~BlockQueue()
	{
		pthread_mutex_destroy(&lock);
		pthread_cond_destroy(&full);
		pthread_cond_destroy(&empty);
	}
};
pthread_t c, p;
pthread_create(&c, NULL, consumer, (void*)&bq);
pthread_create(&p, NULL, producter, (void*)&bq);

pthread_join(c, NULL);
pthread_join(p, NULL);
return 0;
}

void* consumer(void* arg)
{
	BlockQueue* bqp = (BlockQueue*)arg;
	int data;
	for (; ; ) 
	{
		bqp->PopData(data);
		std::cout << "Consume data done : " << data << std::endl;
	}
}

//more faster
void* producter(void* arg)
{
	BlockQueue* bqp = (BlockQueue*)arg;
	srand((unsigned long)time(NULL));
	for (; ; ) 
	{
		int data = rand() % 1024;
		bqp->PushData(data);
		std::cout << "Prodoct data done: " << data << std::endl;
		// sleep(1);
	}
}
int main()
{
	BlockQueue bq;
	pthread_t c, p;

	pthread_create(&c, NULL, consumer, (void*)&bq);
	pthread_create(&p, NULL, producter, (void*)&bq);

	pthread_join(c, NULL);
	pthread_join(p, NULL);
	return 0;
}

3.POSIX信号量

      POSIX 信号量和 SystemV 信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但 POSIX 可以用于线程间同步。

3.1 初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
    pshared:0表示线程间共享,非零表示进程间共享
    value:信号量初始值

3.2 销毁信号量

int sem_destroy(sem_t *sem);

3.3 等待信号量

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()

3.4 发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()

4.基于环形队列的生产消费模型

  • 环形队列采用数组模拟,用模运算来模拟环状特性
  • 环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态

  • 但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>

#define NUM 16

class RingQueue 
{
private:
	std::vector<int> q;
	int cap;
	sem_t data_sem;
	sem_t space_sem;
	int consume_step;
	int product_step;

public:
	RingQueue(int _cap = NUM) :q(_cap), cap(_cap)
	{
		sem_init(&data_sem, 0, 0);
		sem_init(&space_sem, 0, cap);
		consume_step = 0;
		product_step = 0;
	}
	void PutData(const int& data)
	{
		sem_wait(&space_sem); // P
		q[consume_step] = data;
		consume_step++;
		consume_step %= cap;
		sem_post(&data_sem); //V
	}
	void GetData(int& data)
	{
		sem_wait(&data_sem);
		data = q[product_step];
		product_step++;
		product_step %= cap;
		sem_post(&space_sem);
	}
	~RingQueue()
	{
		sem_destroy(&data_sem);
		sem_destroy(&space_sem);
	}
};
void* consumer(void* arg)
{
	RingQueue* rqp = (RingQueue*)arg;
	int data;
	for (; ; ) 
	{
		rqp->GetData(data);
		std::cout << "Consume data done : " << data << std::endl;
		sleep(1);
	}
}

//more faster
void* producter(void* arg)
{
	RingQueue* rqp = (RingQueue*)arg;
	srand((unsigned long)time(NULL));
	for (; ; ) 
	{
		int data = rand() % 1024;
		rqp->PutData(data);
		std::cout << "Prodoct data done: " << data << std::endl;
		// sleep(1);
	}
}

int main()
{
	RingQueue rq;
	pthread_t c, p;
	pthread_create(&c, NULL, consumer, (void*)&rq);
	pthread_create(&p, NULL, producter, (void*)&rq);
	pthread_join(c, NULL);
	pthread_join(p, NULL);
}

5.线程池

/*threadpool.h*/
/* 线程池:
* 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着
监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利
用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
* 线程池的应用场景:
* 1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技
术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个
Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
* 2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
* 3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情
况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,
出现错误.
* 线程池的种类:
* 线程池示例:
* 1. 创建固定数量线程池,循环从任务队列中获取任务对象,
* 2. 获取到任务对象后,执行任务对象中的任务接口
*/

/*threadpool.hpp*/
#ifndef __M_TP_H__
#define __M_TP_H__
#include <iostream>
#include <queue>
#include <pthread.h>

#define MAX_THREAD 5

typedef bool (*handler_t)(int);

class ThreadTask
{
private:
	int _data;
	handler_t _handler;
public:
	ThreadTask() :_data(-1), _handler(NULL)
	{}

	ThreadTask(int data, handler_t handler)
	{
		_data = data;
		_handler = handler;
	}
	void SetTask(int data, handler_t handler)
	{
		_data = data;
		_handler = handler;
	}
	void Run() 
	{
		_handler(_data);
	}
};

class ThreadPool
{
private:
	int _thread_max;
	int _thread_cur;
	bool _tp_quit;
	std::queue<ThreadTask*> _task_queue;
	pthread_mutex_t _lock;
	pthread_cond_t _cond;
private:
	void LockQueue() 
	{
		pthread_mutex_lock(&_lock);
	}
	void UnLockQueue()
	{
		pthread_mutex_unlock(&_lock);
	}
	void WakeUpOne() 
	{
		pthread_cond_signal(&_cond);
	}
	void WakeUpAll() 
	{
		pthread_cond_broadcast(&_cond);
	}
	void ThreadQuit() 
	{
		_thread_cur--;
		UnLockQueue();
		pthread_exit(NULL);
	}
	void ThreadWait()
	{
		if (_tp_quit)
		{
			ThreadQuit();
		}
		pthread_cond_wait(&_cond, &_lock);
	}
	bool IsEmpty() {
		return _task_queue.empty();
	}
	static void* thr_start(void* arg)
	{
		ThreadPool* tp = (ThreadPool*)arg;
		while (1) 
		{
			tp->LockQueue();
			while (tp->IsEmpty())
			{
				tp->ThreadWait();
			}
			ThreadTask* tt;
			tp->PopTask(&tt);
			tp->UnLockQueue();
			tt->Run();
			delete tt;
		}
		return NULL;
	}
public:
	ThreadPool(int max = MAX_THREAD) :_thread_max(max), _thread_cur(max),
		_tp_quit(false) 
	{
		pthread_mutex_init(&_lock, NULL);
		pthread_cond_init(&_cond, NULL);
	}
	~ThreadPool() 
	{
		pthread_mutex_destroy(&_lock);
		pthread_cond_destroy(&_cond);
	}
	bool PoolInit() 
	{
		pthread_t tid;
		for (int i = 0; i < _thread_max; i++) 
		{
			int ret = pthread_create(&tid, NULL, thr_start, this);
			if (ret != 0) {
				std::cout << "create pool thread error\n";
				return false;
			}
		}
		return true;
	}
	bool PushTask(ThreadTask* tt)
	{
		LockQueue();
		if (_tp_quit) 
		{
			UnLockQueue();
			return false;
		}
		_task_queue.push(tt);
		WakeUpOne();
		UnLockQueue();
		return true;
	}
	bool PopTask(ThreadTask** tt) 
	{
		*tt = _task_queue.front();
		_task_queue.pop();
		return true;
	}
	bool PoolQuit()
	{
		LockQueue();
		_tp_quit = true;
		UnLockQueue();
		while (_thread_cur > 0) 
		{
			WakeUpAll();
			usleep(1000);
		}
		return true;
	}
};
#endif


/*main.cpp*/
bool handler(int data)
{
	srand(time(NULL));
	int n = rand() % 5;
	printf("Thread: %p Run Tast: %d--sleep %d sec\n", pthread_self(), data, n);
	sleep(n);
	return true;
}

int main()
{
	int i;

	ThreadPool pool;
	pool.PoolInit();
	for (i = 0; i < 10; i++)
	{
		ThreadTask* tt = new ThreadTask(i, handler);
		pool.PushTask(tt);
	}
	pool.PoolQuit();
	return 0;
}
g++ -std=c++0x test.cpp -o test -pthread -lrt

6.线程安全的单例模式

6.1 什么是单例模式

     单例模式是一种 " 经典的 , 常用的 , 常考的 " 设计模式。

6.2 什么是设计模式

     IT 行业这么火 , 涌入的人很多 . 俗话说林子大了啥鸟都有 . 大佬和菜鸡们两极分化的越来越严重 . 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景 , 给定了一些对应的解决方案 , 这个就是 设计模式。

6.3 单例模式的特点

      某些类 , 只应该具有一个对象 ( 实例 ), 就称之为单例。例如一个男人只能有一个媳妇。在很多服务器开发场景中, 经常需要让服务器加载很多的数据 ( 上百 G) 到内存中。 此时往往要用一个单例的类来管理这些数据。

6.4 饿汉实现方式和懒汉实现方式

吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.
吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.
       懒汉方式最核心的思想是 " 延时加载 ". 从而能够优化服务器的启动速度。

6.5 饿汉方式实现单例模式

template <typename T>
class Singleton 
{
	static T data;
public:
	static T* GetInstance()
	{
		return &data;
	}
};
    只要通过 Singleton 这个包装类来使用 T 对象 , 则一个进程中只有一个 T 对象的实例。

6.6 懒汉方式实现单例模式

template <typename T>
class Singleton
{
	static T* inst;
public:
	static T* GetInstance() 
	{
		if (inst == NULL)
		{
			inst = new T();
		}
		return inst;
	}
};
    存在一个严重的问题 , 线程不安全。 第一次调用 GetInstance 的时候 , 如果两个线程同时调用 , 可能会创建出两份 T 对象的实例。 但是后续再次调用, 就没有问题了。

6.7 懒汉方式实现单例模式(线程安全版本) 

// 懒汉模式, 线程安全
template <typename T>
class Singleton 
{
	volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.
	static std::mutex lock;
public:
	static T* GetInstance() 
	{
		if (inst == NULL) 
		{ // 双重判定空指针, 降低锁冲突的概率, 提高性能.
			lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new.
			if (inst == NULL) 
			{
				inst = new T();
			}
			lock.unlock();
		}
		return inst;
	}
};
     注意事项:
  1. 加锁解锁的位置
  2. 双重 if 判定 , 避免不必要的锁竞争
  3. volatile关键字防止过度优化

7. STL,智能指针和线程安全

7.1 STL中的容器是否是线程安全的?

    不是 . 原因是, STL 的设计初衷是将性能挖掘到极致 , 而一旦涉及到加锁保证线程安全 , 会对性能造成巨大的影响 . 而且对于不同的容器, 加锁方式的不同 , 性能可能也不同 ( 例如 hash 表的锁表和锁桶 ). 因此 STL 默认不是线程安全 . 如果需要在多线程环境下使用 , 往往需要调用者自行保证线程安全 .

 7.2 智能指针是否是线程安全的?

     对于 unique_ptr, 由于只是在当前代码块范围内生效 , 因此不涉及线程安全问题 . 对于shared_ptr, 多个对象需要共用一个引用计数变量 , 所以会存在线程安全问题 . 但是标准库实现的时候考虑到了这
个问题 , 基于原子操作 (CAS) 的方式保证 shared_ptr 能够高效 , 原子的操作引用计数 .

 7.3 其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS 操作。
  • CAS 操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
  • 自旋锁,公平锁,非公平锁?

 8.  读者写者问题

8.1 读写锁

     在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。

  •  注意:写独占,读共享,读锁优先级高

8.2  读写锁接口

8.2.1 设置读写优先

int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
/*
pref 共有 3 种选择

PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和
PTHREAD_RWLOCK_PREFER_READER_NP 一致

PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁
*/

8.2.2 初始化

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t
*restrict attr);

8.2.3 销毁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

8.2.4 加锁和解锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

8.2.5 读写锁案例

#include <vector>
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <pthread.h>


volatile int ticket = 1000;
pthread_rwlock_t rwlock;

void* reader(void* arg)
{
	char* id = (char*)arg;
	while (1)
	{
		pthread_rwlock_rdlock(&rwlock);
		if (ticket <= 0) 
		{
			pthread_rwlock_unlock(&rwlock);
			break;
		}
		printf("%s: %d\n", id, ticket);
		pthread_rwlock_unlock(&rwlock);
		usleep(1);
	}
	return nullptr;
}

void* writer(void* arg)
{
	char* id = (char*)arg;
	while (1) 
	{
		pthread_rwlock_wrlock(&rwlock);
		if (ticket <= 0) 
		{
			pthread_rwlock_unlock(&rwlock);
			break;
		}
		printf("%s: %d\n", id, --ticket);
		pthread_rwlock_unlock(&rwlock);
		usleep(1);
	}
	return nullptr;
}

struct ThreadAttr
{
	pthread_t tid;
	std::string id;
};


std::string create_reader_id(std::size_t i)
{
	// 利用 ostringstream 进行 string 拼接
	std::ostringstream oss("thread reader ", std::ios_base::ate);
	oss << i;
	return oss.str();
}


std::string create_writer_id(std::size_t i)
{
	// 利用 ostringstream 进行 string 拼接
	std::ostringstream oss("thread writer ", std::ios_base::ate);
	oss << i;
	return oss.str();
}


void init_readers(std::vector<ThreadAttr>& vec)
{
	for (std::size_t i = 0; i < vec.size(); ++i) 
	{
		vec[i].id = create_reader_id(i);
		pthread_create(&vec[i].tid, nullptr, reader, (void*)vec[i].id.c_str());
	}
}


void init_writers(std::vector<ThreadAttr>& vec)
{
	for (std::size_t i = 0; i < vec.size(); ++i) 
	{
		vec[i].id = create_writer_id(i);
		pthread_create(&vec[i].tid, nullptr, writer, (void*)vec[i].id.c_str());
	}
}


void join_threads(std::vector<ThreadAttr> const& vec)
{
	// 我们按创建的 逆序 来进行线程的回收
	for (std::vector<ThreadAttr>::const_reverse_iterator it = vec.rbegin(); it !=
		vec.rend(); ++it) 
	{
		pthread_t const& tid = it->tid;
		pthread_join(tid, nullptr);
	}
}


void init_rwlock()
{
#if 0 // 写优先
	pthread_rwlockattr_t attr;
	pthread_rwlockattr_init(&attr);
	pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
	pthread_rwlock_init(&rwlock, &attr);
	pthread_rwlockattr_destroy(&attr);
#else // 读优先,会造成写饥饿
	pthread_rwlock_init(&rwlock, nullptr);
#endif
}

int main()
{
	// 测试效果不明显的情况下,可以加大 reader_nr
	// 但也不能太大,超过一定阈值后系统就调度不了主线程了
	const std::size_t reader_nr = 1000;
	const std::size_t writer_nr = 2;

	std::vector<ThreadAttr> readers(reader_nr);
	std::vector<ThreadAttr> writers(writer_nr);

	init_rwlock();

	init_readers(readers);
	init_writers(writers);

	join_threads(writers);
	join_threads(readers);

	pthread_rwlock_destroy(&rwlock);
}
main: main.cpp
      g++ -std=c++11 -Wall -Werror $^ -o $@ -lpthread

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/354266.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【淄博正大光明】收藏|三分钟带你全面了解这个神奇的镜片

对于孩子的东西 家长总是谨慎再谨慎 而对于夜间戴在眼睛里的镜片 家长更是存在很多顾虑 安全吗&#xff1f;有效吗&#xff1f; 影响孩子睡觉吗&#xff1f; 别着急淄博正大光明眼科医院 带你深度了解角膜塑形镜 01 角膜塑形镜究竟是什么&#xff1f; 角膜塑形镜是一种使用高分…

一文搞懂Linux内核进程CPU调度基本原理

为什么需要调度 进程调度的概念比较简单&#xff0c;我们假设在一个单核处理器的系统中&#xff0c;同一时刻只有一个进程可以拥有处理器资源&#xff0c;那么其他的进程只能在就绪队列中等待&#xff0c;等到处理器空闲之后才有计划获得处理器资源来运行。在这种场景下&#…

k8s快速入门

文章目录一、Kubernetes&#xff08;K8S&#xff09;简介1、概念1.1 Kubernetes (K8S) 是什么1.2 核心特性1.3 部署方案2、Kubernetes 集群架构2.1 架构2.2 重要概念 Pod2.3 Kubernetes 组件二、Kubernetes集群安装1、安装方式介绍2、minikubute安装3、裸机搭建&#xff08;Bar…

python实用脚本(六)—— pandas库的使用(生成、读取表格)

本期主题&#xff1a; python的pandas使用 往期链接&#xff1a; python实用脚本&#xff08;一&#xff09;—— 批量修改目标文件夹下的文件名python实用脚本&#xff08;二&#xff09;—— 使用xlrd读取excelpython实用脚本&#xff08;三&#xff09;—— 通过有道智云AP…

Linux 日志查找常用命令

1.1 cat、zcat cat -n app.log | grep "error"&#xff1a;查询日志中含有某个关键字error的信息&#xff0c;显示行号。 cat -n app.log | grep "error" --color&#xff1a;查询日志中含有某个关键字error的信息&#xff0c;显示行号&#xff0c;带颜色…

基于Detectron2模型和深度学习方法的改进森林火灾检测方法

1.文章信息本次介绍的文章是来自韩国科研团队的一篇2023年火灾检测文章&#xff0c;文章立足于森林火灾检测&#xff0c;题目为《An Improved Forest Fire Detection Method Based on the Detectron2 Model and a Deep Learning Approach》。2.摘要随着全球变暖和人口的增加&am…

【java】真正理解NIO

文章目录前言1、线程不够用, 就算使用了线程池复用线程也无济于事;2、阻塞I/O模式下,会有大量的线程被阻塞,一直在等待数据,这个时候的线程被挂起,只能干等,CPU利用率很低,换句话说,系统的吞吐量差;3、如果网络I/O堵塞或者有网络抖动或者网络故障等,线程的阻塞时间可能很长。整…

Spring boot实现热部署

1.说明 在我们进行Spring Boot项目的编写过程中&#xff0c;会有局部的代码&#xff0c;发生一些变动&#xff0c;这时候&#xff0c;我们只有将项目重启&#xff0c;发生变动的代码才能够生效&#xff0c;为了解决这个问题&#xff0c;我们可以设置Spring Boot热部署&#xf…

React Hooks之useRef详解

一、什么是useRef const refContainer useRef(initialValue); useRef 返回一个可变的 ref 对象&#xff0c;其内部只有一个 current 属性被初始化为传入的参数&#xff08;initialValue&#xff09;useRef 返回的 ref 对象在组件的整个生命周期内持续存在更新 current 值时并不…

Java 洛谷 P1739 表达式括号匹配

题目描述&#xff1a; 题目链接&#xff1a;https://www.luogu.com.cn/problem/P1739 代码实例&#xff1a; import java.util.Scanner; import java.util.Stack;public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);String…

本地生成动漫风格 AI 绘画 图像|Stable Diffusion WebUI 的安装和部署教程

Stable Diffusion WebUI 的安装和部署教程1. 简介2. Windows安装环境3. 运行4. 模型下载链接5. 其他资源1. 简介 先放一张WebUI的图片生成效果图&#xff0c;以给大家学习的动力 &#xff1a;&#xff09; 怎么样&#xff0c;有没有小小的心动&#xff1f;这里再补充一下&…

Linux文件系统中的硬链接及常见面试题

如果能对inode的概念有所了解&#xff0c;对理解本文会有所帮助。如果对inode的概念不太清楚也没有关系&#xff0c;我们会捎带介绍一下。在文件系统的实现层面&#xff0c;我们可以认为包含两个组件&#xff1a;一个是包含数据块的池子&#xff0c;池子中的数据块是等大小的&a…

K3S 系列文章-5G IoT 网关设备 POD 访问报错 DNS ‘i/o timeout‘分析与解决

开篇 《K3s 系列文章》《Rancher 系列文章》 问题概述 20220606 5G IoT 网关设备同时安装 K3S Server, 但是 POD 却无法访问互联网地址&#xff0c;查看 CoreDNS 日志提示如下&#xff1a; ... [ERROR] plugin/errors: 2 update.traefik.io. A: read udp 10.42.0.3:38545-&…

进程间通信(重点)

概念 进程是一个独立的资源分配单元&#xff0c;不同进程之间的资源是独立的进程并非孤立的&#xff0c;不同进程需要进行信息的交互和状态的传递&#xff0c;因此需要进程之间的通信【IPC: Inter processes communication】 如qq聊天&#xff0c;qq在每个人的手机上是独立的…

记录--滚动视差动画和解决方法

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 最简单的代码&#xff0c;最极致的享受&#xff0c;主打的就是一个炫酷&#xff5e; 滚动视差 滚动视差效果(Parallax Scrolling)是指让多层背景以不同的速度位移&#xff0c;形成立体的运动效果的视觉…

代码随想录算法训练营第三天 | 链表理论基础 、203.移除链表元素、707.设计链表、206.反转链表

打卡第三天&#xff0c;今天还是认真做了两道题目&#xff0c;明天要6点早起&#xff0c;想早点睡&#xff0c;加上昨天的螺旋数组&#xff08;昨天想到怎么做&#xff0c;但是代码实现解不出来&#xff0c;还没有仔细看视频讲解&#xff09;&#xff0c;和今天的设计链表&…

内网渗透(三十九)之横向移动篇-pass the ticket 票据传递攻击(PTT)横向攻击

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

Pytorch 物体检测 App 体验

物体检测 App 介绍 它是使用 YOLOv5 进行对象检测的 Android 示例应用程序&#xff0c;使用 PyTorch 脚本化 YOLOv5 模型来检测使用该模型训练的 80 个物体对象。 YOLO&#xff08;You Only Look Once&#xff09;是最快和最受欢迎的对象检测模型之一&#xff0c;而YOLOv5 是…

pytorch零基础实现语义分割项目(三)——语义分割模型(U-net和deeplavb3+)

文章目录项目列表前言U-net模型概况下采样过程上采样过程模型代码上采样代码U-net模型构建deeplabv3模型概况模型代码resNetASPPdeeplabv3模型构建结尾项目列表 语义分割项目&#xff08;一&#xff09;——数据概况及预处理 语义分割项目&#xff08;二&#xff09;——标签…

简单的组合拳

前言&#xff1a;在最近的wxb举行hw中&#xff0c;同事让我帮他看看一些后台登录站点。尝试了未授权&#xff0c;弱口令皆无果&#xff0c;要么不存在弱口令&#xff0c;要么有验证码&#xff0c;没办法绕过。本文章仅提供一个思路&#xff0c;在hw中更多时候并不推荐尝试这种思…