【在Linux世界中追寻伟大的One Piece】多线程(三)

news2024/11/30 6:13:50

目录

1 -> Linux线程同步

1.1 -> 条件变量

1.2 -> 同步概念与竞态条件

1.3 -> 条件变量函数

1.4 -> 为什么pthread_cond_wait需要互斥量

1.5 -> 条件变量使用规范

2 -> 生产者消费者模型

2.1 -> 为什么要使用生产者消费者模型

2.2 -> 生产者消费者模型优点

2.3 -> 基于BlockingQueue的生产者消费者模型

2.4 -> C++ queue模拟阻塞队列的生产消费模型

2.5 -> POSIX信号量

2.6 -> 基于环形队列的生产消费模型

3 -> 线程池

3.1 -> 线程池概念

3.2 -> 线程池应用场景

3.3 -> 线程池示例

4 -> 线程安全的单例模式

4.1 -> 什么是单例模式

4.2 -> 单例模式的特点

4.3 -> 饿汉方式实现单例模式

4.4 -> 懒汉方式实现单例模式

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

5 -> STL,智能指针和线程安全

5.1 -> STL中的容器是否是线程安全的

5.2 -> 智能指针是否是线程安全的


1 -> Linux线程同步

1.1 -> 条件变量

  • 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
  • 例如一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

1.2 -> 同步概念与竞态条件

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。
  • 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题不难理解。

1.3 -> 条件变量函数

初始化

int pthread_cond_init(pthread_cond_t* restrict cond, const pthread_condattr_t* restrict attr);

参数:
cond:要初始化的条件变量
attr:NULL

销毁

int pthread_cond_destroy(pthread_cond_t* cond);

等待条件满足

int pthread_cond_wait(pthread_cond_t* restrict cond, pthread_mutex_t* restrict mutex);

参数:
cond:要在这个条件变量上等待
mutex:互斥量

唤醒等待

int pthread_cond_broadcast(pthread_cond_t* cond);
int pthread_cond_signal(pthread_cond_t* cond);

简单案例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

pthread_cond_t cond;
pthread_mutex_t mutex;

void* r1(void* arg)
{
	while (1) 
	{
		pthread_cond_wait(&cond, &mutex);
		printf("活动\n");
	}
}

void* r2(void* arg)
{
	while (1) 
	{
		pthread_cond_signal(&cond);
		sleep(1);
	}
}

int main(void)
{
	pthread_t t1, t2;

	pthread_cond_init(&cond, NULL);
	pthread_mutex_init(&mutex, NULL);

	pthread_create(&t1, NULL, r1, NULL);
	pthread_create(&t2, NULL, r2, NULL);

	pthread_join(t1, NULL);
	pthread_join(t2, NULL);

	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
}
[root@localhost linux]# ./a.out
活动
活动
活动

1.4 -> 为什么pthread_cond_wait需要互斥量

  • 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
  • 条件不会无缘无故的突然变得满足,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。

  • 按照上面的说法,设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上就行了。
// 错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false) 
{
	pthread_mutex_unlock(&mutex);
	//解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过
	pthread_cond_wait(&cond);
	pthread_mutex_lock(&mutex);

}
pthread_mutex_unlock(&mutex);
  • 由于解锁和等待不是原子操作。调用解锁之后,pthread_cond_wait之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么pthread_cond_wait将错过这个信号,可能会导致线程永远阻塞在这个pthread_cond_wait。所以解锁和等待必须是一个原子操作。
  • int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);进入该函数后,会去看条件量是否等于0,等于0,就把互斥量变成1,直到cond_wait返回,把条件量改成1,把互斥量恢复成原样。

1.5 -> 条件变量使用规范

  • 等待条件代码

pthread_mutex_lock(&mutex);
while (条件为假)
	pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);
  • 给条件发送信号代码
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

2 -> 生产者消费者模型

2.1 -> 为什么要使用生产者消费者模型

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

2.2 -> 生产者消费者模型优点

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

2.3 -> 基于BlockingQueue的生产者消费者模型

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

2.4 -> 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);
	}
};

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;
}

2.5 -> POSIX信号量

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

初始化信号量

#include <semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);

参数:
pshared : 0 表示线程间共享,非零表示进程间共享
value:信号量初始值

销毁信号量

int sem_destroy(sem_t* sem);

等待信号量

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

发布信号量

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

生产者-消费者的例子是基于queue的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序(POSIX信号量)。

2.6 -> 基于环形队列的生产消费模型

  • 环形队列采用数组模拟,用模运算来模拟环状特性。

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

  • 但是现有的信号量这个计数器,是简单的进行多进程间的同步过程。
#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);

}

3 -> 线程池

3.1 -> 线程池概念

一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络 sockets 等的数量。

3.2 -> 线程池应用场景

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

3.3 -> 线程池示例

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

4 -> 线程安全的单例模式

4.1 -> 什么是单例模式

单例模式(Singleton Pattern)是一种设计模式,旨在确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式常用于管理共享资源,如配置信息、线程池、缓存等。单例模式的核心思想是控制对象的实例化过程,使得在整个应用程序中只有一个实例存在,并且所有对该实例的访问都通过同一个访问点进行。

4.2 -> 单例模式的特点

  1. 唯一性:单例模式确保一个类只有一个实例。这意味着在整个应用程序的生命周期中,无论在何处调用该类的实例,都将返回同一个对象。
  2. 全局访问点:单例模式提供了一个全局访问点,通常是一个静态方法,用于获取该类的唯一实例。这使得在程序的任何地方都能够方便地访问该实例。
  3. 延迟初始化:单例模式的实例通常在第一次被请求时才会创建,这有助于节省资源。这种延迟初始化的特性也被称为懒加载(Lazy Initialization)。
  4. 线程安全:在多线程环境下,单例模式需要确保其唯一性和全局访问的正确性。这通常通过同步机制来实现,如使用锁或其他并发控制手段。
  5. 控制实例化:单例模式的构造函数通常是私有的,这防止了外部代码通过常规的构造函数创建新的实例。
  6. 可扩展性:单例模式可以通过继承或其他方式进行扩展,以满足不同的应用需求。
  7. 资源管理:单例模式常用于管理共享资源,如数据库连接、线程池、配置信息等,确保这些资源在整个应用程序中只有一个实例,从而提高资源的使用效率和管理便利性。
  8. 性能优化:由于单例模式只创建一个实例,因此可以减少内存开销和提高性能,特别是在处理大量数据或频繁访问的对象时。
  9. 简化代码结构:单例模式可以简化代码结构,因为它提供了一个单一的、全局的访问点,使得代码的维护和理解更加容易。
  10. 适用场景:单例模式适用于那些在整个应用程序中只需要一个实例的场景,如日志记录器、配置管理器、数据库连接池等。

4.3 -> 饿汉方式实现单例模式

template <typename T>
class Singleton 
{
	static T data;
public:
	static T* GetInstance() 
	{
		return &data;
	}
};

只要通过Singleton这个包装类来使用T对象,则一个进程中只有一个T对象的实例。

4.4 -> 懒汉方式实现单例模式

template <typename T>
class Singleton 
{
	static T* inst;
public:
	static T* GetInstance() 
	{
		if (inst == NULL) 
		{
			inst = new T();
		}
		return inst;
	}
};

存在一个严重的问题,线程不安全。

第一个调用GetInstance时,如果两个线程同时调用,可能会创建出两份T对象的实例。

但是后续再次调用,就没有问题了。

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

// 懒汉模式, 线程安全
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关键字防止过度优化

5 -> STL,智能指针和线程安全

5.1 -> STL中的容器是否是线程安全的

不是。

原因是,STL的设计初衷是将性能挖掘到极致,而一旦涉及到加锁保证线程安全,会对性能造成巨大的影响。

而且对于不同的容器,加锁方式的不同,性能可能也不同(例如hash表的锁表和锁桶)。

因此STL默认不是线程安全。如果需要在多线程环境下使用,往往需要调用者自行保证线程安全。

5.2 -> 智能指针是否是线程安全的

对于unique_ptr,由于只是在当前代码块范围内生效,因此不涉及线程安全问题。

对于shared_ptr,多个对象需要共用一个引用计数变量,所以会存在线程安全问题。但是标准库实现的时候考虑到了这个问题,基于原子操作(CAS)的方式保证shared_ptr能够高效,原子的操作引用计数。


感谢各位大佬支持!!!

互三啦!!!

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

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

相关文章

AI数据分析工具(一)

Looker Studio&#xff08;谷歌&#xff09;-免费 优点 免费使用&#xff1a;对于中小型企业和个人用户来说&#xff0c;没有任何费用压力&#xff0c;可以免费享受到数据可视化和报表创建的功能。与Google服务集成&#xff1a;特别适合使用Google产品生态的企业&#xff0c;…

个人博客接入github issue风格的评论,utteranc,gitment

在做个人博客的时候&#xff0c;如果你需要评论功能&#xff0c;但是又不想构建用户体系和评论模块&#xff0c;那么可以直接使用github的issue提供的接口&#xff0c;对应的开源项目有utteranc和gitment&#xff0c;尤其是前者。 它们的原理是一样的&#xff1a;在博客文章下…

springboot 配置跨域访问

什么是 CORS&#xff1f; CORS&#xff0c;全称是“跨源资源共享”&#xff08;Cross-Origin Resource Sharing&#xff09;&#xff0c;是一种Web应用程序的安全机制&#xff0c;用于控制不同源的资源之间的交互。 在Web应用程序中&#xff0c;CORS定义了一种机制&#xff0…

探索Python WebSocket新境界:picows库揭秘

文章目录 探索Python WebSocket新境界&#xff1a;picows库揭秘第一部分&#xff1a;背景介绍第二部分&#xff1a;picows库概述第三部分&#xff1a;安装picows库第四部分&#xff1a;简单库函数使用方法第五部分&#xff1a;场景应用第六部分&#xff1a;常见Bug及解决方案第…

【Linux】-学习笔记06

第二章、时间同步服务器 2.1时间同步服务器的使用 2.1.1系统时区时间的管理 timedatectl set-time "2024-02-13 10:41:55" ##设定系统时间 timedatectl list-timezones ##显示系统的所有时区 timedatectl set-timezone "Asia/Shangh…

Mac使用charles抓包

一、官网下载安装 二、配置Help--->SSL Proxying 有证书选择全部信任即可 三、设置系统代理&#xff0c;mac每次重启都需要选择&#xff0c;否则会没有数据 四、设置端口&#xff08;如果无法获取https&#xff09; 五、手机链接&#xff0c;从网页下载证书保存到手机&…

3d扫描建模产品开发-三维扫描检测蓝光检测

现当下&#xff0c;汽车制造、航空航天&#xff0c;还是消费电子、医疗器械&#xff0c;三维扫描检测与蓝光技术正以前所未有的精度和效率&#xff0c;推动着产品从概念到实物的快速转化。 三维扫描技术&#xff0c;简而言之&#xff0c;就是通过激光、结构光&#xff08;如蓝…

Hive中的基本数据类型和表的类型

Hive支持关系数据库中大多数据基本数据类型&#xff0c;同时还支持三种复杂类型。 示例&#xff1a; Hive表 创建表 – 直接建表法 create table t_page_view ( page_id bigint comment ‘页面ID’, page_name string comment ‘页面名称’, page_url string comment ‘页面…

Python PDF转JPG图片小工具

Python PDF转JPG图片小工具 1.简介 将单个pdf装换成jpg格式图片 Tip: 1、软件窗口默认最前端&#xff0c;不支持调整窗口大小&#xff1b; 2、可通过按钮选择PDF文件&#xff0c;也可以直接拖拽文件到窗口&#xff1b; 3、转换质量有5个档位&#xff0c;&#xff08;0.25&a…

剖析 SpringBoot 于夕阳红公寓管理系统架构搭建的核心作用

3 系统分析 本文作者在确定了研究的课题之后&#xff0c;从各大数字图书馆下载文献来阅读&#xff0c;并了解同类型的网站具备的大致功能&#xff0c;然后与本系统用户的实际需求结合进行分析&#xff0c;得出本系统要研究的具体功能与性能。虽然分析系统这一阶段性工作主要是确…

springboot336社区物资交易互助平台pf(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 社区物资交易互助平台设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff…

嵌入式linux系统中网络编程的具体实现

1.网络编程简介 要编写通过计算机网络通信的程序,首先要确定这些程序同通信的协议(protocol),在设计一个协议的细节之前,首先要分清程序是由哪个程序发起以及响应何时产生。 举例来说,一般认为WEB服务器程序是一个长时间运行的程序(守护进程deamon),它只在响应来自网…

vue3.0 根据富文本html页面生成压缩包(含视频在线地址、图片在线地址、前端截图、前端文档)

vue3.0生成压缩包&#xff08;含在线地址、前端截图、前端文档&#xff09; 需求描述效果开始下载插件包基本代码构造 点击下载按钮1.截图content元素&#xff0c;并转化为pdfcanvas putImageData、getImageDatagetImageData 获取指定矩形区域的像素信息putImageData 将这些数据…

由于导包而引发的错误

今天在调试时发现删除功能无论如何都无法实现&#xff0c;于是调试找到了mapper层的错误但不知道为什么报错。以下是报错信息。 Caused by: org.apache.ibatis.binding.BindingException: Parameter userIds not found. Available parameters are [arg0, collection, list]at o…

黑马2024AI+JavaWeb开发入门Day04-SpringBootWeb入门-HTTP协议-分层解耦-IOCDI飞书作业

视频地址&#xff1a;哔哩哔哩 讲义作业飞书地址&#xff1a;day04作业&#xff08;IOC&DI&#xff09; 作业很简单&#xff0c;主要是练习拆分为三层架构controller、service、dao&#xff0c;并基于IOC & DI进行解耦。 1、结构&#xff1a; 2、代码 网盘链接&…

【LeetCode: 145. 二叉树的后序遍历 + 栈】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

哈希表算法题

目录 题目一——1. 两数之和 - 力扣&#xff08;LeetCode&#xff09; 1.1.暴力解法1 1.2.暴力解法2 1.2.哈希表解法 题目二——面试题 01.02. 判定是否互为字符重排 - 力扣&#xff08;LeetCode&#xff09; 题目三——217. 存在重复元素 - 力扣&#xff08;LeetCode&…

Galaxy预测比特币期权活跃交易将持续至2027年,特朗普执政中期

随着比特币及其相关产品的交易量不断增加&#xff0c;加密货币市场的期权交易也迎来了前所未有的活跃。Galaxy Digital的交易团队指出&#xff0c;贝莱德IBIT ETF期权在美国股市的交易量已经创下了新纪录。首日交易量高达353,716份合约&#xff0c;几乎与2012年Facebook期权上线…

Java-GUI(登录界面示例)

简述&#xff1a; 步骤&#xff1a; (1)构造界面(将组件对象加入容器对象,注意&#xff1a;应设定对容器对象的布局策略&#xff09; (2)为界面加入事件响应处理(如单击按钮&#xff09; 实现&#xff1a; 两种方式实现&#xff0c;只有用户名为"admin"且密码为…

屏幕分辨率|尺寸|颜色深度指纹

一、前端通过window.screen接口获取屏幕分辨率 尺寸 颜色深度&#xff0c;横屏竖屏信息。 二、window.screen c接口实现&#xff1a; 1、third_party\blink\renderer\core\frame\screen.idl // https://drafts.csswg.org/cssom-view/#the-screen-interface[ExposedWindow ] …