【Linux】线程详解之线程互斥与同步

news2024/11/22 16:32:46

文章目录

  • Linux线程互斥
    • 一、进程线程间的互斥相关概念
      • 1.临界资源和临界区
      • 2.互斥和原子性
    • 二、互斥量mutex
      • 1.抢票程序是否引入互斥量现象观察
      • 2.抢票程序原理分析
      • 3.互斥量的接口
      • 4. 加锁后的程序
      • 5.互斥量原理探究
  • 可重入VS线程安全
    • 一、概念
      • 1.线程安全
      • 2.重入
    • 二、常见的线程不安全的情况
    • 三、常见的线程安全的情况
    • 四、常见不可重入的情况
    • 五、常见可重入的情况
    • 六、可重入与线程安全联系
    • 七、可重入与线程安全区别
  • 常见锁概念
    • 一、死锁
    • 二、死锁四个必要条件
    • 三、避免死锁
  • Linux线程同步
    • 一、同步概念与竞态条件
    • 二、条件变量
      • 1. 条件变量函数初始化
      • 2.条件变量函数销毁
      • 3.条件变量函数等待条件满足
      • 4.唤醒等待
      • 5.程序示例
    • 三、为什么 pthread_cond_wait 需要互斥量?

Linux线程互斥

一、进程线程间的互斥相关概念

临界资源:多线程执行流共享的资源就叫做临界资源
临界区:每个线程内部,访问临界资源的代码,就叫做临界区
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

1.临界资源和临界区

在前边学习进程的时候,我们实现进程间通信可以使用管道,共享内存等方式,此时就是多执行流在共享资源,所以此时的管道或共享内存就是临界资源,而访问临界资源的代码就被称为临界区。

例如在我们的实际生活中,当我们要看演唱会时,需要进行抢票,如果多个人可以同时买票,并且不进行限制,很有可能有多个人抢到同一张票,所以在以后演出开始之后,很多人只能抢同一个座位,这是不能被允许的,所以就必须加以限制。

2.互斥和原子性

互斥就像是一把锁,当一个人在抢这张票时,其他人就被锁上了,不能再去抢这张票,回到我们线程中,当多个线程进行竞争时,当一个线程拿到这把锁,其他线程就不能再去访问该临界资源了。

原子性就是站在其他线程的角度来看,要么某一线程还没有开始访问临界资源,要么已经访问完成,这是两种对其他线程有利的情况,而并不关心是否在进行访问临界资源。

二、互斥量mutex

通过前边的学习,我们知道了线程之间的通信十分简单,除了线程的栈空间和上下文数据是私有的,其他的数据和内容都是共享的,所以我们定义全局变量就可以直接进行通信,但是在可以进行通信之后,也会带来很多问题,例如可能多个线程在同一时刻发送数据或接收数据,多个线程并发操作,可能会导致数据异常,所以为了解决这个问题,我们就引入互斥量的概念。

1.抢票程序是否引入互斥量现象观察

我们通过演唱会抢票的例子来学习互斥量:


首先来看看不引入互斥量的情况:

#include <iostream>
#include <cstdio>
#include <string>
#include <ctime>
#include <mutex>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class tickets
{
private:
    int ticket;
    pthread_mutex_t mx;

public:
    tickets()
        : ticket(1000)
    {
        //初始化互斥锁
        pthread_mutex_init(&mx, nullptr);
    }
    ~tickets()
    {
        //销毁互斥锁
        pthread_mutex_destroy(&mx);
    }
    bool buy_tickets()
    {
        bool ret = true;
        if (ticket > 0)
        {
            usleep(1000);
            cout << "我是[" << pthread_self() << "]线程,我正在抢第" << ticket << "张票" << endl;
            ticket--;
            printf("");
        }
        else
        {
            cout << "票已经卖完了" << endl;
            ret = false;
        }
        return ret;
    }
};
void *pthread_run(void *args)
{
    tickets *t = (tickets *)args;
    while (true)
    {
        if (!t->buy_tickets())
            break;
    }
}
int main()
{
   tickets *t = new tickets();
    pthread_t tid[5];
    for (int i = 0; i < 5; i++)
    {
        pthread_create(tid + i, nullptr, pthread_run, (void *)t);
    }
    for (int i = 0; i < 5; i++)
    {
        pthread_join(tid[i], nullptr);
    }
    return 0;
}

在这里插入图片描述
在不引入互斥量时,我们会发现惊奇的现象,就是可能会有多个线程抢同一张票,导致最后会出现负数票的情况,这是万万不能接受的。


接下来,我们再来看一下引入互斥量的现象:

#include <iostream>
#include <cstdio>
#include <string>
#include <ctime>
#include <mutex>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class tickets
{
private:
    int ticket;
    pthread_mutex_t mx;

public:
    tickets()
        : ticket(1000)
    {
        //初始化互斥锁
        pthread_mutex_init(&mx, nullptr);
    }
    ~tickets()
    {
        //销毁互斥锁
        pthread_mutex_destroy(&mx);
    }
    bool buy_tickets()
    {
        bool ret = true;
        //加锁
        pthread_mutex_lock(&mx);
        if (ticket > 0)
        {
            usleep(1000);
            cout << "我是[" << pthread_self() << "]线程,我正在抢第" << ticket << "张票" << endl;
            ticket--;
            printf("");
        }
        else
        {
            cout << "票已经卖完了" << endl;
            ret = false;
        }
        //解锁
        pthread_mutex_unlock(&mx);
        return ret;
    }
};
void *pthread_run(void *args)
{
    tickets *t = (tickets *)args;
    while (true)
    {
        if (!t->buy_tickets())
            break;
    }
}
int main()
{
    tickets *t = new tickets();
    //tickets* t;
    pthread_t tid[5];
    for (int i = 0; i < 5; i++)
    {
        pthread_create(tid + i, nullptr, pthread_run, (void *)t);
    }
    for (int i = 0; i < 5; i++)
    {
        pthread_join(tid[i], nullptr);
    }
    return 0;
}

在这里插入图片描述
在引入互斥量之后,就不会出现有多个线程同时抢一张票而导致最后出现负数的情况。

2.抢票程序原理分析

为什么会出现负票数呢?

1.if 语句判断条件为真以后,代码可以并发的切换到其他线程
2.usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
3.–ticket 操作本身就不是一个原子操作

有很多人可能会说,–ticket不就是一条语句吗?为什么不是原子性的呢?

这是因为–ticket在C语言中虽然是一条语句,但是转换成汇编就不是一条语句了,可能是多条语句,而在多条语句执行时,就有可能会有其他线程访问该代码段,我通过下图来详细解释:

先来看–ticket转换为汇编的代码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


此时,我们必须知道,虽然寄存器是被每一个线程共享的,但是寄存器中的数据确实私有的,当某一个线程时间片到了之后被切走,会将寄存器中的数据也带走保存在自己的上下文当中,所以此时在进行–ticket操作时就有可能出现下边的情况:
步骤一:
在这里插入图片描述
步骤二:
在这里插入图片描述
步骤三:
在这里插入图片描述


经过上边的三步,我们就会发现,–ticket并不是原子性的,可能在执行某一行代码时,可能该线程被切走,所以导致数据异常,出现负数票的情况。


要解决以上问题,需要做到三点:

代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。
加粗样式

3.互斥量的接口

初始化互斥量
初始化互斥量有两种方法:

方法1,静态分配:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

方法2,动态分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict
attr);
参数
mutex:要初始化的互斥量
attr:初始化互斥量的属性,默认写成NULL

销毁互斥量
销毁互斥量需要注意:

使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
不要销毁一个已经加锁的互斥量
已经销毁的互斥量,要确保后面不会有线程再尝试加锁

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 时,可能会遇到以下情况:
互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

4. 加锁后的程序

#include <iostream>
#include <cstdio>
#include <string>
#include <ctime>
#include <mutex>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class tickets
{
private:
    int ticket;
    pthread_mutex_t mx;

public:
    tickets()
        : ticket(1000)
    {
        //初始化互斥锁
        pthread_mutex_init(&mx, nullptr);
    }
    ~tickets()
    {
        //销毁互斥锁
        pthread_mutex_destroy(&mx);
    }
    bool buy_tickets()
    {
        bool ret = true;
        //加锁
        pthread_mutex_lock(&mx);
        if (ticket > 0)
        {
            usleep(1000);
            cout << "我是[" << pthread_self() << "]线程,我正在抢第" << ticket << "张票" << endl;
            ticket--;
            printf("");
        }
        else
        {
            cout << "票已经卖完了" << endl;
            ret = false;
        }
        //解锁
        pthread_mutex_unlock(&mx);
        return ret;
    }
};
void *pthread_run(void *args)
{
    tickets *t = (tickets *)args;
    while (true)
    {
        if (!t->buy_tickets())
            break;
    }
}
int main()
{
    tickets *t = new tickets();
    pthread_t tid[5];
    for (int i = 0; i < 5; i++)
    {
        pthread_create(tid + i, nullptr, pthread_run, (void *)t);
    }
    for (int i = 0; i < 5; i++)
    {
        pthread_join(tid[i], nullptr);
    }
    return 0;
}

在这里插入图片描述

5.互斥量原理探究

经过上边的证明,我们知道了ticket–或者ticket++都不是原子性的,那么为什么加入一个锁可以保证原子性呢?
这是因为在一批线程来竞争某一资源,当其中一个线程先申请到锁时,此时其他线程再来申请锁,锁告诉线程,不好意思,锁已经属于别人了,此时无论哪个线程再来申请,都不可能申请成功,只有当拥有锁的线程将锁释放,也就是归还之后,其他的线程才可能申请成功,这样就保证了互斥性和原子性。
在这里插入图片描述
但是我们又忽视了一个情况,需要访问临界资源,必须先来申请锁,所以锁是肯定要被所有线程看到了,那么问题来了,锁不就是一个临界资源吗?而且处理成汇编也不只是一个语句,那么锁是原子性的吗?
来分析一下汇编代码:

第一条指令:
将寄存器al中的值清0
第二条指令:
交换内存中mutex的值和寄存器中的值
第三步:
判断锁的值,若大于0,则可以访问临界资源,若小于0,则挂起等待。

所以mutex的初始值为1,当一个线程来申请锁时,这个线程的al寄存器会将0换给mutex,而al的值变为1,当第二个线程来申请锁,先将al寄存器的值清零,然后与mutex交换,得到的值还是0
,所以就会挂起等待。哪怕拿到锁的线程暂时被挂起了,也会将al寄存器的值保存在自己的上下文当中,也就是将锁带走了,其他线程也不能申请到锁。
在这里插入图片描述


在这里插入图片描述

可重入VS线程安全

一、概念

1.线程安全

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

2.重入

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

二、常见的线程不安全的情况

不保护共享变量的函数
函数状态随着被调用,状态发生变化的函数
返回指向静态变量指针的函数
调用线程不安全函数的函数

三、常见的线程安全的情况

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

四、常见不可重入的情况

调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
可重入函数体内使用了静态的数据结构

五、常见可重入的情况

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

六、可重入与线程安全联系

函数是可重入的,那就是线程安全的
函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

七、可重入与线程安全区别

可重入函数是线程安全函数的一种
线程安全不一定是可重入的,而可重入函数则一定是线程安全的
如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

常见锁概念

一、死锁

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

并且,单执行流也可以造成死锁,例如下边的程序:

#include<iostream>
#include<pthread.h>
using namespace std;

pthread_mutex_t mtx;

void* thread_run(void* args)
{
    pthread_mutex_lock(&mtx);
    Spthread_nutex_lock(&mtx);
    pthread_exit((void*)0);
    // char* name = (char*)args;
    // cour<<i am a <<name<<endl;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,thread_run,(void*)"new thread");
    pthread_mutex_init(&mtx,nullptr);
    pthread_join(tid,nullptr);
    pthread_mutex_destroy(&mtx);
    return 0;
}

在这里插入图片描述

二、死锁四个必要条件

互斥条件:一个资源每次只能被一个执行流使用
请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

也就是说如果出现了死锁,就一直达到了以上的条件。

三、避免死锁

破坏死锁的四个必要条件
加锁顺序一致
避免锁未释放的场景
资源一次性分配

Linux线程同步

一、同步概念与竞态条件

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

我们前边提到了,线程可以访问同一份资源,可能会出现数据异常的问题,所以我们引入了互斥的概念,通过加锁来使同一时间只有一个线程访问临界资源,当加锁之后,可能又会因为某一线程的竞争力过强,一直处于加锁和销毁锁的状态,导致其他线程不能去访问临界资源而导致饥饿问题,所以我们就要引入线程同步的概念,当一个线程在销毁锁之后,不能立马去申请锁,而是要到队列的末尾排序,进入条件变量的等待队列,不会造成其他的线程长时间不能访问临界资源的情况。

二、条件变量

条件变量是一种同步机制,用于在多个线程之间进行通信。它允许一个线程等待另一个线程满足特定的条件,然后再继续执行。条件变量通常与互斥锁一起使用,以确保线程安全。条件变量提供了一种高效的方式来实现线程之间的同步和通信。

当临界资源加锁之后,我们不容易知道临界资源的状态,这时我们可以引入临界变量来获得临界资源的状态,下边我来举一个生活中的例子:

例如,我们要去书店买一本《剑指offer》,但是去了书店之后,售货员说书卖完了,但是过两天可能会到货,现在我们回家之后,只能等待,那么我们怎么知道书什么时候才有呢?这是有两种方式,第一种方式就是我们没事就去书店一趟,问一下售货员书来了吗,或者是留下售货员的电话,当书来了之后,让售货员给我们打电话,这种打电话的方式我们就可以简单的认为就是使用条件变量的方式来获得临界资源的状态。

1. 条件变量函数初始化

与互斥锁的初始化类似:

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
参数:
cond:要初始化的条件变量
attr:条件变量的相关属性,默认给NULL

2.条件变量函数销毁

int pthread_cond_destroy(pthread_cond_t *cond)

3.条件变量函数等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释

4.唤醒等待

第一个是唤醒等待一批线程

int pthread_cond_broadcast(pthread_cond_t *cond)

第二个是唤醒等待一个线程

int pthread_cond_signal(pthread_cond_t *cond).

5.程序示例

我们现在实现一个老板控制工人工作的例子,来观察一个线程可以通过条件变量来控制其他的线程。

#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;

pthread_mutex_t mx;
pthread_cond_t cond;

void* worker_routine(void* args)
{
    while(true)
    {
        int num = *(int*)args;
        pthread_cond_wait(&cond,&mx);
        cout<<"worker"<<num<<"is working..."<<endl;
    }

}
void* boss_routine(void* args)
{
    while(true)
    {
        cout<<"boss say:";
        pthread_cond_signal(&cond);
        sleep(1);
    }
}
int main()
{
    pthread_t boss;
    pthread_t tid[3];
    pthread_mutex_init(&mx,nullptr);
    pthread_cond_init(&cond,nullptr);
    for(int i=0;i<3;i++)
    {
        int* num = new int(i);
        pthread_create(tid+i,nullptr,worker_routine,(void*)num);
    }
    pthread_create(&boss,nullptr,boss_routine,(void*)"boss");

    for(int i=0;i<3;i++)
    {
        pthread_join(tid[i],nullptr);
    }
    pthread_join(boss,nullptr);

    return 0;
}

在这里插入图片描述
在这里插入图片描述

三、为什么 pthread_cond_wait 需要互斥量?

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

在这里插入图片描述

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

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

相关文章

【P37】JMeter 仅一次控制器(Once Only Controller)

文章目录 一、仅一次控制器&#xff08;Once Only Controller&#xff09;参数说明二、测试计划设计2.1、测试计划一2.1、测试计划二 一、仅一次控制器&#xff08;Once Only Controller&#xff09;参数说明 可以让控制器内部的逻辑只执行一次&#xff1b;单次的范围是针对某…

Spring Authorization Server 系列(一)环境搭建

Spring Authorization Server 中的scope参数解析 前提依赖版本问题确定Spring Boot 版本确定 Spring Authorization Server 版本最终的依赖 第一个 Spring Authorization Server Demo 前提 由于 Spring 最新的 OAuth2 解决方案 已经由 Spring Security 下的 OAuth2 模块独立出…

从零开始学习JavaScript:轻松掌握编程语言的核心技能①

从零开始学习JavaScript&#xff1a;轻松掌握编程语言的核心技能 一&#xff0c;JavaScript 简介为什么学习 JavaScript?JavaScript 用法 二&#xff0c;JavaScript 输出JavaScript 显示数据JavaScript&#xff1a;直接写入 HTML 输出流 三&#xff0c;JavaScript 语法JavaScr…

VS2022发布独立部署的.net程序

.net core支持依赖框架部署和独立部署两种方式&#xff0c;之前学习时是在VSCode中使用dotnet命令发布的。但是在VS2022中却不知道该如何设置。以获取PDF文件使用字体的项目为例&#xff0c;VS2022中默认编译的是依赖框架部署方式&#xff08;编译的结果如下图所示&#xff09;…

Android进阶 View事件体系(三):典型的滑动冲突情况和解决策略

Android进阶 View事件体系&#xff08;三&#xff09;&#xff1a;典型的滑动冲突情况和解决策略 内容概要 本篇文章为总结View事件体系的第三篇文章&#xff0c;前两篇文章的在这里&#xff1a; Android进阶 View事件体系&#xff08;一&#xff09;&#xff1a;概要介绍和实…

Oracle——数据操纵DML(一)

STU1 1、不指定字段的整行插入 在STU1中新增一名同学的基本信息&#xff0c;SQL如下&#xff1a; INSERT INTO test.stu1 VALUES(0001,牛牛,男,24,to_date(1988-05-25,YYYY-MM-DD),12外语)格式如下&#xff1a; INSERT INTO 表名 VALUES(值1,值2,...,值n)对于CHAR或VARCHAR等…

sql-labs SQL注入平台——第二关Less-2 GET - Error based - Intiger based (基于错误的GET整型注入)

Less-2 GET - Error based - Intiger based (基于错误的GET整型注入) 一、先确认漏洞是否存在 &#xff08;1&#xff09;查询 id1返回查询结果正常 &#xff08;2&#xff09;查询 id1’返回查询结果报错&#xff0c;可能存在SQL注入 &#xff08;3&#xff09;查询 id1 …

路径规划算法:基于帝国主义竞争优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于帝国主义竞争优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于帝国主义竞争优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用…

Dubbo环境搭建

1.搭建zookeeper注册中心环境 zookeeper下载地址 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ptMw7rb-1685261782669)(b894c0cbb6501ca97145d3b09685ae8f.png)] 在bin文件下&#xff0c;启动zkServer.cmd会有报错&#xff0c;处理需要在condi…

你所不知道的 数据在内存中储存 的来龙去脉

那么好了好了&#xff0c;宝子们&#xff0c;今天给大家介绍一下 “数据在内存中储存” 的来龙去脉&#xff0c;来吧&#xff0c;开始整活&#xff01;⛳️ 一、数据类型的介绍 &#xff08;1&#xff09;整型和浮点型&#xff1a; &#xff08;2&#xff09;其他类型…

Linux——使用命令行参数管理环境变量

目录 使用命令行参数获取用户在DOS命令行输入的指令&#xff1a; 方法&#xff1a;代码如下&#xff1a; 使用命令行参数获取并打印部分或者整体环境变量的方法&#xff1a; 方法1&#xff1a; 运行结果&#xff1a; 方法2&#xff1a;使用外部链接environ: 使用命令行参数…

如何开发背包系统?

UE5 插件开发指南 前言0 背包数据结构1 背包管理组件2 背包UI显示前言 相信大家对于背包系统并不陌生,谁还没有玩过几款游戏呢?游戏中的背包都是大同小异的,朴素的功能就是存放我们获取到的物品,高级一点就是要有物品分类,便于玩家刷选背包中的物品,能够显示玩家拥有的货…

2023 牛津大学博士后申请指南

牛津大学是全球著名的高等教育机构&#xff0c;其博士后项目备受瞩目。为了帮助申请者更好地了解牛津大学博士后申请流程&#xff0c;本文将介绍该校博士后申请指南的相关内容。一、申请条件首先&#xff0c;申请者必须已经获得博士学位或即将完成博士学位&#xff0c;并具有相…

《数据库应用系统实践》------ 报刊销售系统

系列文章 《数据库应用系统实践》------ 报刊销售系统 文章目录 系列文章一、需求分析1、系统背景2、 系统功能结构&#xff08;需包含功能结构框图和模块说明&#xff09;3&#xff0e;系统功能简介 二、概念模型设计1&#xff0e;基本要素&#xff08;符号介绍说明&#xff…

k8s 对已完成job自动清理

job在处理完一个任务以后&#xff0c;状态会变成Completed&#xff0c;job在状态为Completed的时候默认不会自动清理的&#xff0c;还会继续占用系统资源。 TTL-after-finished控制器 kubernetes中有专门的控制器可以自动清理已完成的job,就是TTL-after-finished控制器。 TTL…

数据中心产业如何变革?中国数字能源生态大会这些观点值得一读

对于数据中心产业&#xff0c;这是一个最好的时代。数字经济的蓬勃发展&#xff0c;推动产业数字化、企业数字化转型步入纵深阶段&#xff0c;大幅增加数据中心等基础设施的需求&#xff0c;让数据中心产业迎来前所未有的市场良机。 与此同时&#xff0c;对于数据中心产业&…

【备战秋招】每日一题:4月1日美团春招(二批)第一题:题面+题目思路 + C++/python/js/Go/java带注释

2023大厂笔试模拟练习网站&#xff08;含题解&#xff09; www.codefun2000.com 最近我们一直在将收集到的各种大厂笔试的解题思路还原成题目并制作数据&#xff0c;挂载到我们的OJ上&#xff0c;供大家学习交流&#xff0c;体会笔试难度。现已录入200道互联网大厂模拟练习题&…

使用render平台免费部署自己的ChatGPT

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

解决websocket在部署到服务器https下无法使用的问题

目录 一、问题 1.1 问题描述 1.2 问题详细描述 二、解决 2.1 https下的链接类型 2.2 修改Nginx的配置 一、问题 1.1 问题描述 一个小项目中使用到了websocket&#xff0c;这个websocket在本地完全是完全正常运行的&#xff0c;不管是前后台的信息通讯 还是 异常报错接收…

JavaScript教程(高级)

面向对象编程介绍 两大编程思想 &#xff08;1&#xff09;、 面向过程编程&#xff1a; &#xff08;缩写 POP&#xff09;&#xff08; Process-oriented programming&#xff09;面向过程就是分析出解决问题所需要的步骤&#xff0c;然后用函数把这些步骤一步一步实现&am…