线程及其应用

1.线程概念及其用途和优缺点

2.互斥量(锁)

3.条件变量

4.POSIX信号量

5.生产者消费者模型

6.线程池

7.线程安全的单例模式

1.线程概念及其用途和优缺点

概念:线程是比进程更轻量化的一种执行流,是在进程内部执行的一种执行流。线程是CPU调度的基本单位,进程是承担系统资源的基本实体。注意,在Linux中,没有真正意义上的线程,都是通过使用轻量化进程模拟出来的。

优点:创建一个新线程的代价要比创建一个新进程小得多;

        与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多;

        线程占用的资源要比进程少很多;

        能充分利用多处理器的可并行数量;

        在等待慢速I/O操作结束的同时,程序可执行其他的计算任务;

        计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现;

        I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

缺点:性能损失, 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型 线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的 同步和调度开销,而可用的资源不变。

        健壮性降低, 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了 不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

        缺乏访问控制, 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

        编程难度提高, 编写与调试一个多线程程序比单线程程序困难得多。

用途:合理的使用多线程,能提高CPU密集型程序的执行效率 ;合理的使用多线程,能提高IO密集型程序的用户体验。

线程函数:

创建线程:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);

参数: thread:返回线程ID ;attr:设置线程的属性,attr为NULL表示使用默认属性 ;start_routine:是个函数地址,线程启动后要执行的函数; arg:传给线程启动函数的参数; 返回值:成功返回0;失败返回错误码。

获取线程自身ID:pthread_t pthread_self(void);

线程终止:void pthread_exit(void *value_ptr);

参数: value_ptr:value_ptr不要指向一个局部变量。 返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)。

取消一个执行中的线程:int pthread_cancel(pthread_t thread);

参数 thread:线程ID; 返回值:成功返回0;失败返回错误码。

等待线程结束:int pthread_join(pthread_t thread, void **value_ptr);

参数: thread:线程ID; value_ptr:它指向一个指针,后者指向线程的返回值; 返回值:成功返回0;失败返回错误码。

分离线程:int pthread_detach(pthread_t thread);

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放 资源,从而造成系统泄漏。 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。joinable和分离是冲突的,一个线程不能既是joinable又是分离的。

#include<pthread.h>
#include<unistd.h>
#include<cstdio>
#include<cstdlib>int ticket=10000;
void*geticket();
void*Function(void*arg)
{geticket();
}void*geticket()
{while(true){if(ticket>0){usleep(1000);cout<<pthread_self()<<": get a ticket: "<<ticket<<endl;ticket--;}else{break;}}
}int main()
{pthread_t id1,id2;pthread_create(&id1,nullptr,Function,(void*)"thread1");pthread_join(id1,nullptr);pthread_detach(id1);return 0;
}

2.互斥量(锁)

临界资源:多线程执行流共享的资源就叫做临界资源。

临界区:每个线程内部,访问临界资源的代码,就叫做临界区。

互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。

原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

互斥量mutex :大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个 线程,其他线程无法获得这种变量。 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之 间的交互。 多个线程并发的操作共享变量,会带来一些问题。

例如在上面的代码中,若是使用多个线程进行geticket就会出现ticket出现负数的情况,为防止出现这种情况,我们需要一把锁即互斥量。

互斥量具有以下作用:当代码进入临界区执行时,不允许其他线程进入该临界区。

                                    如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临 界区。

                                    如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

互斥量函数:

初始化互斥量:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER  静态分配

                        int pthread_mutex_init(pthread_mutex_t *restrict mutex, const                         pthread_mutexattr_t *restrict attr);  动态分配

参数: mutex:要初始化的互斥量; attr:NULL

销毁互斥量:int pthread_mutex_destroy(pthread_mutex_t *mutex);

                     静态初始化的互斥量不需要销毁;不要销毁一个已经加锁的互斥量。

互斥量加锁和解锁:int pthread_mutex_lock(pthread_mutex_t *mutex); 

                                 int pthread_mutex_unlock(pthread_mutex_t *mutex);

                                返回值:成功返回0,失败返回错误号。

                                互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功。 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量, 那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

#include<pthread.h>
#include<unistd.h>
#include<cstdio>
#include<cstdlib>pthread_mutex_t mutex=PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP; 
int ticket=10000;
void*geticket();
void*Function(void*arg)
{geticket();
}void*geticket()
{while(true){pthread_mutex_lock(&mutex);if(ticket>0){usleep(1000);cout<<pthread_self()<<": get a ticket: "<<ticket<<endl;ticket--;pthread_mutex_unlock(&mutex);}else{break;}}
}int main()
{pthread_t id1,id2;pthread_create(&id1,nullptr,Function,(void*)"thread1");pthread_create(&id2,nullptr,Function,(void*)"thread2");pthread_join(id1,nullptr);pthread_join(id2,nullptr);pthread_detach(id1);pthread_detach(id2);pthread_mutex_destroy(&mutex);return 0;
}

线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作, 并且没有锁保护的情况下,会出现该问题。

常见的线程安全的情况:每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的; 类或者接口对于线程来说都是原子操作; 多个线程之间的切换不会导致该接口的执行结果存在二义性。

可重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们 称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重 入函数,否则,是不可重入函数。

常见的可重入情况:不使用全局变量或静态变量; 不使用用malloc或者new开辟出的空间; 不调用不可重入函数; 不返回静态或全局数据,所有数据都有函数的调用者提供; 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。

线程安全与否,描述的是线程的状态。可/不可重入,描述的是函数的特点。

可重入函数是线程安全函数的一种,线程安全函数不一定是可重入函数。

死锁:死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资 源而处于的一种永久等待状态。

死锁的四个必要条件:互斥条件:一个资源每次只能被一个执行流使用。

                                   请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放。

                                  不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺。

                                  循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系。

避免死锁及其算法:破坏死锁的四个必要条件; 加锁顺序一致; 避免锁未释放的场景; 资源一次性分配;死锁检测算法; 银行家算法。

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);随机唤醒一个

wait函数执行后,首先会释放mutex锁,并使调用线程进入阻塞状态和等待cond被触发,这个mutex锁会被其他线程锁住,等到signal函数被调用后,会发送信号给等待cond的线程,mutex被再次释放。

注意:条件变量一般与互斥量一起使用,因为条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥量的不足。

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

4.POSIX信号量

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

注意:信号量本质是一把计数器,申请信号就是预约资源。

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()

P()V()操作是原子的。

5.生产者消费者模型

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

优点:解耦 ;支持并发,提高效率; 支持忙闲不均。

“三种关系,两个角色,一个场所”:

三种关系:生产者之间的关系是互斥关系;消费者之间的关系是互斥关系;生产者和消费者之间的关系是互斥和同步关系。

两个角色:生产者和消费者。

一个场所:内存空间。

生产者消费者模型一般有两种:其一是基于BlockingQueue的生产者消费者模型,其一是基于环形队列(RingQueue)的生产者消费者模型。二者的主要区别是前者是通过条件变量和互斥量进行构建的,后者是通过互斥量和POSIX信号量进行构建的。

//生产消费模型:普通版本
#include <iostream>
#include <queue>
#include <pthread.h>
#include <thread>
#include <mutex>
#include "Task.hpp"using namespace std;
template<class T>
class BlockQueue
{
public:BlockQueue(int cap=5):_cap(cap){pthread_mutex_init(&lock,nullptr);pthread_cond_init(&full,nullptr);pthread_cond_init(&empty,nullptr);}~BlockQueue(){pthread_mutex_destroy(&lock);pthread_cond_destroy(&full);pthread_cond_destroy(&empty);}bool Isfull(){return q.size()==_cap;}bool Isempty(){return q.size()==0;}void Push(const T&in)//生产者的{pthread_mutex_lock(&lock);while(Isfull()){pthread_cond_wait(&full,&lock);}q.push(in);pthread_cond_signal(&empty);pthread_mutex_unlock(&lock);}void Pop(T*out)//消费者的{pthread_mutex_lock(&lock);while(Isempty()){pthread_cond_wait(&empty,&lock);}*out=q.front();q.pop();pthread_cond_signal(&full);pthread_mutex_unlock(&lock);}
private:queue<T> q;pthread_mutex_t lock;pthread_cond_t full;pthread_cond_t empty;int _cap;
};
//生产消费模型:环形队列
#pragma once#include <iostream>
#include <vector>
#include <semaphore.h>
#include <mutex>template<class T>
class RingQueue
{
private:void P(sem_t&sem){sem_wait(&sem);}void V(sem_t&sem){sem_post(&sem);}
public:RingQueue(int _size=10):v(_size),size(_size),c_step(0),p_step(0){pthread_mutex_init(&c_lock,nullptr);pthread_mutex_init(&p_lock,nullptr);sem_init(&c_sem,0,0);sem_init(&p_sem,0,size);}~RingQueue(){pthread_mutex_destroy(&c_lock);pthread_mutex_destroy(&p_lock);sem_destroy(&c_sem);sem_destroy(&p_sem);}void Push(const T&in){P(p_sem);{pthread_mutex_lock(&p_lock);v[p_step]=in;p_step++;p_step%=size;}V(c_sem);pthread_mutex_unlock(&p_lock);}void Pop(T*out){P(c_sem);{pthread_mutex_lock(&c_lock);*out=v[c_step];c_step++;c_step%=size;}V(p_sem);pthread_mutex_unlock(&c_lock);}
private:vector<T> v;int size;int c_step;int p_step;sem_t c_sem;sem_t p_sem;pthread_mutex_t c_lock;pthread_mutex_t p_lock;
};

6.线程池

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

线程池的应用场景:  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技 术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个 Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。  

3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情 况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限, 出现错误.

线程池实现: 1. 创建固定数量线程池,循环从任务队列中获取任务对象;2. 获取到任务对象后,执行任务对象中的任务接口

//PthreadPool.h
class Date
{
public:Date(const string &name) : threadname(name){}~Date(){}public:string threadname;
};template <class T>
class PthreadPool
{
private:PthreadPool(int thread_num = 5) : _thread_num(thread_num){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);for (int i = 1; i < _thread_num; i++){string name = "thread-";name += std::to_string(i);Date td(name);_threads.emplace_back(name,bind(&PthreadPool<T>::ThreadRun, this,placeholders::_1),td);cout<<name.c_str()<<" is created..."<<endl;}}PthreadPool(const PthreadPool<T> &tp) = delete;const PthreadPool<T> &operator=(const PthreadPool<T>) = delete;public:static PthreadPool<T> *GetInstance(){if (instance == nullptr){LockGuard lockguard(&sig_lock);if (instance == nullptr){cout<<"创建单例成功..."<<endl;instance = new PthreadPool<T>();}}return instance;}bool Start(){// 启动for (auto &thread : _threads){thread.Start();cout<<thread.ThreadName()<<" is running..."<<endl;}return true;}void ThreadWait(const Date &td){cout<<td.threadname.c_str()<<" no task,is sleeping..."<<endl;pthread_cond_wait(&_cond, &_mutex);}void ThreadWakeup(){pthread_cond_signal(&_cond);}void ThreadRun(Date &td){while (true){// 取任务T t;{LockGuard lockguard(&_mutex);while (_q.empty()){ThreadWait(td);cout<<(td.threadname).c_str()<<" thread is wakeup"<<endl;}t = _q.front();_q.pop();}// 处理任务t();cout<<td.threadname<<" habdler task "<<t.PrintTask().c_str()<<" done,result is "<<t.PrintResult().c_str()<<endl;}}void Push(T &in){cout<<"other thread push a task,task is :"<<in.PrintTask().c_str()<<endl;LockGuard lockguard(&_mutex);_q.push(in);ThreadWakeup();}~PthreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}void Wait(){for (auto &thread : _threads){thread.Join();}}private:queue<T> _q;vector<Thread<Date>> _threads;int _thread_num;pthread_mutex_t _mutex;pthread_cond_t _cond;static PthreadPool<T> *instance;static pthread_mutex_t sig_lock;
};template <class T>
PthreadPool<T> *PthreadPool<T>::instance = nullptr;
template <class T>
pthread_mutex_t PthreadPool<T>::sig_lock = PTHREAD_MUTEX_INITIALIZER;
//thread.h
#pragma once#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
template<class T>
using func_t = std::function<void(T&)>;template<class T>
class Thread
{
public:Thread(const std::string &threadname, func_t<T> func, T &data):_tid(0), _threadname(threadname), _isrunning(false), _func(func), _data(data){}static void *ThreadRoutine(void *args) {Thread *ts = static_cast<Thread *>(args);ts->_func(ts->_data);return nullptr;}bool Start(){int n = pthread_create(&_tid, nullptr, ThreadRoutine, this);if(n == 0) {_isrunning = true;return true;}else return false;}bool Join(){if(!_isrunning) return true;int n = pthread_join(_tid, nullptr);if(n == 0){_isrunning = false;return true;}return false;}std::string ThreadName(){return _threadname;}bool IsRunning(){return _isrunning;}~Thread(){}
private:pthread_t _tid;std::string _threadname;bool _isrunning;func_t<T> _func;T _data;
};

7.线程安全的单例模式

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

饿汉方式实现单例模式:

懒汉方式最核心的思想是 "延时加载". 从而能够优化服务器的启动速度。

template <class T>
class PthreadPool
{
public:static PthreadPool<T> *GetInstance(){if (instance == nullptr){LockGuard lockguard(&sig_lock);if (instance == nullptr){cout<<"创建单例成功..."<<endl;instance = new PthreadPool<T>();}}return instance;}
private:queue<T> _q;vector<Thread<Date>> _threads;int _thread_num;pthread_mutex_t _mutex;pthread_cond_t _cond;static PthreadPool<T> *instance;static pthread_mutex_t sig_lock;
};template <class T>
PthreadPool<T> *PthreadPool<T>::instance = nullptr;
template <class T>
pthread_mutex_t PthreadPool<T>::sig_lock = PTHREAD_MUTEX_INITIALIZER;

懒汉方式实现单例模式:

template <typename T>
class Singleton
{volatile static T *inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.static std::mutex lock;public:static T *GetInstance(){if (inst == NULL){lock.lock();if (inst == NULL){inst = new T();}lock.unlock();}return inst;}
};

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

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

相关文章

Redis数据类型深掘:如何有效使用List,Set和Sorted Set

List(列表) List 数据类型的深度解析 定义复习 Redis的List数据类型是一个字符串的序列集合,这些字符串按照插入的顺序进行排序。得益于它的设计,List允许重复的值出现,并支持两端的推入(push)和弹出(pop)操作。这种数据类型非常适合用作堆栈(Stack)或队列(Queue)。 …

基于工业互联网打造敏捷供应链的实现方式:创新路径与实践应用

引言 工业互联网和敏捷供应链是当今制造业发展中的两个重要概念。工业互联网以数字化、网络化和智能化为核心&#xff0c;致力于将传统工业生产与互联网技术相融合&#xff0c;从而实现生产过程的高效、智能和灵活。而敏捷供应链则强调快速响应市场需求、灵活调整生产和供应计划…

fastadmin批量导入

表的字段必须备注清楚导出的excel表头必须对应上如果mysql表有约束&#xff0c;导入会自动限制&#xff0c;挺方便的一个功能。

上心师傅的思路分享(二)

Druid monitor 与Springboot常见报错界面渗透小技巧 目录 前言 1.Druid monitor介绍 2.Druid未授权(1rank) 3.druid弱口令 4.Druid进一步利用 4.1 URL监控 4.2 Session监控 利用思路 EditThisCookie(小饼干插件) 5.SpringBoot Actuator未授权访问漏洞 5.1 简介 5…

微信群聊天机器人怎么搭建

要使用 chatgpt-on-wechat 项目搭建一个微信群聊机器人并获取群聊信息&#xff0c;请按照以下步骤操作&#xff1a; 克隆仓库&#xff1a; git clone https://github.com/zhayujie/chatgpt-on-wechat cd chatgpt-on-wechat/安装依赖&#xff1a; pip3 install -r requirements.…

深入理解Linux中的`as`命令:汇编器之旅

标题&#xff1a;深入理解Linux中的as命令&#xff1a;汇编器之旅 在Linux的世界中&#xff0c;编程和编译过程通常涉及多个步骤&#xff0c;从源代码到可执行文件&#xff0c;每一步都至关重要。其中一个重要的步骤是将汇编代码转换为机器代码&#xff0c;这通常是由汇编器&a…

Spark MLlib机器学习

前言 随着大数据时代的到来&#xff0c;数据处理和分析的需求急剧增加&#xff0c;传统的数据处理工具已经难以满足海量数据的分析需求。Apache Spark作为一种快速、通用的集群计算系统&#xff0c;迅速成为了大数据处理的首选工具。而在Spark中&#xff0c;MLlib&#xff08;…

【Java数据结构】详解LinkedList与链表(三)

&#x1f512;文章目录&#xff1a; 1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; 2.无头双向非循环链表的实现 2.1成员属性 2.2成员方法 display——打印链表 size——获取单链表长度 addFirst——头插 addLast——尾插 addIndex——在任…

c++ new 和 malloc 分配内存

创建一个类 class Test { public:Test() { std::cout << "constructor"; }virtual ~Test() {}void print() { std::cout << "a:" << a; }private:int a 10; };main函数 int main(int argv, char **args) {std::cout << "c…

Application UI

本节包含关于如何用DevExpress控件模拟许多流行的应用程序ui的教程。 Windows 11 UI Windows 11和最新一代微软Office产品启发的UI。 Office Inspired UI Word、Excel、PowerPoint和Visio等微软Office应用程序启发的UI。 How to: Build an Office-inspired UI manually 本教…

数据分析中的统计学基础及Python具体实现【数据分析】

各位大佬好 &#xff0c;这里是阿川的博客&#xff0c;祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 Python 初阶 Python–语言基础与由来介绍 Python–…

layui左侧菜单栏,鼠标悬停显示菜单文字

layui封装的左侧菜单是固定宽度的&#xff0c;且左侧菜单栏在css里改变宽度&#xff0c;效果并不是很好&#xff08;还设计头部菜单栏&#xff09;&#xff0c;如果写js来让菜单栏能够拉伸&#xff0c;也比较麻烦&#xff0c;那怎么最简单的&#xff0c;让用户看到菜单的文字呢…

从混乱到有序:PDM系统如何优化物料编码

在现代制造业中&#xff0c;物料管理是企业运营的核心。物料编码作为物料管理的基础&#xff0c;对于确保物料的准确性、唯一性和高效性至关重要。随着产品种类的不断增加和产品变型的多样化&#xff0c;传统的物料编码管理方式已经不能满足企业的需求。本文将探讨产品数据管理…

SpringSecurity6从入门到实战之默认用户的生成流程

SpringSecurity6从入门到实战之默认用户的生成流程 这次还是如标题所示,上一章我们的登录页面已经知道是如何生成了.那么,我们通过表单登录的user用户以及密码SpringSecurity是如何进行生成的呢? 默认用户生成 让我们把登录流程重新拉回到读取/META-INF/spring/ .imports文件 …

什么是MES系统?有什么作用?

MES系统解决方案是专门针对制造企业设计&#xff0c;在MES系统的应用下&#xff0c;实现专业的工厂、车间生产信息化管理方案&#xff0c;帮助制造企业提高生产效率。针对目前制造行业的生产管理状况&#xff0c;以及提升企业生产效率和企业竞争力的需求&#xff0c;实施MES系统…

《尚庭公寓》项目部署之Docker + Nginx

docker rmi nginx docker pull nginx docker rm -f nginx #先创建一个简易的nginx容器&#xff08;后面会删&#xff09;&#xff0c;然后通过 docker cp命令把容器里面的nginx配置反向拷贝到宿主主机上。 docker run --name nginx -p 80:80 -d nginx# 将容器nginx.conf文件复…

小猪APP分发:高效的APP托管服务分发平台

有没有遇到过这样的尴尬&#xff1f;辛辛苦苦开发了一个APP&#xff0c;却在托管和分发环节卡壳。想想看&#xff0c;花了那么多时间精力开发的APP&#xff0c;却因为分发不顺利而影响用户体验&#xff0c;实在是让人抓狂。而小猪APP分发就成了你最好的选择。 APP封装分发www.…

pypi 发布自己的包

注册pypi个人用户 网址&#xff1a;https://pypi.org 目录结构dingtalk_utils 必须-pkgs- __init__.py .gitignore LICENSE 必须 README.md 必须 requirements.txt setup.py 必须安装依赖 pip install setuptools wheel安装上传工具 pip install twinesetup.py i…

PHP质量工具系列之php-depend

php-depend是一个开源的静态代码分析工具&#xff0c;它的主要功能包括&#xff1a; 代码质量分析 复杂度度量&#xff1a;计算类、方法和函数的Cyclomatic Complexity&#xff08;循环复杂度&#xff09;&#xff0c;帮助识别潜在的复杂代码段。 耦合度度量&#xff1a;分析类…

推荐网站(20)ai工具集,你想要的ai工具里面都有

今天&#xff0c;我要向您介绍一个综合性的在线平台——AI工具集&#xff0c;这是一个集成了多种人工智能工具的网站&#xff0c;旨在为用户提供一站式的智能解决方案。无论您是专业人士、创意工作者&#xff0c;还是仅仅对AI技术感兴趣的普通用户&#xff0c;AI工具集都能满足…