【Linux】线程的创建、回收分离以及线程的同步互斥

目录

一、多线程的基本编程

二、线程安全(同步互斥)

1.使用互斥锁达到互斥

2.使用互斥锁和条件变量达到线程间的同步互斥


一、多线程的基本编程

线程回收:线程在运行时需要分配内存空间、处理器时间等系统资源,这些资源在线程结束后应当被释放,以便其他线程或进程能够继续使用它们。如果不回收线程,就会导致系统资源的浪费和资源泄漏问题。

1.join回收(常用)

使用 pthread_join 函数可以实现线程的等待和回收效果。具体来说,调用 pthread_join 函数会使当前线程(通常是主线程)等待指定的线程结束执行,然后将其资源回收。

当调用 pthread_join 后,主线程会阻塞直到指定的线程结束。这意味着主线程会等待被等待线程执行完成后再继续执行后续的代码。如果不使用 pthread_join,主线程可能会在被等待线程执行完之前就结束,这样被等待线程的资源可能无法得到回收造成僵尸线程,可能导致资源泄漏。

因此,使用 pthread_join 函数可以确保线程的资源在合适的时候被回收,保证程序的正确性和稳定性。

  1. pthread_create: 这个函数用于创建一个新线程。它接受四个参数:第一个参数是指向线程标识符的指针,用于存储新线程的标识符;第二个参数是用于设置线程属性的指针,通常为 NULL,表示使用默认属性;第三个参数是一个函数指针,指向我们想要在线程中执行的函数;第四个参数是传递给线程函数的参数。

  2. pthread_join: 这个函数用于等待指定的线程结束执行。它接受两个参数:第一个参数是要等待的线程的标识符;第二个参数是一个指向线程返回值的指针。在这个示例中,我们传递了 NULL,表示我们不关心线程的返回值。

  3. pthread_exit: 这个函数用于终止调用它的线程。在线程函数中,当线程完成任务后,应该使用 pthread_exit 来终止线程。

  4. 通常情况下,线程在结束时会调用 pthread_exit 来主动结束自己的执行,并且可以通过传递一个退出状态来向其他线程传递信息。然后,其他线程可以使用 pthread_join 来等待该线程的结束,并获取它的退出状态。所以当一个线程调用 pthread_join 来等待另一个线程结束时,被等待的线程可以通过 pthread_exit 函数传递一个退出状态,这个退出状态会被 pthread_join 函数接收到,并存储在指定的变量中。这样,主线程或者其他等待线程就可以获取到被等待线程的返回值。

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

// 一个简单的线程函数,打印一些文本
void *print_message(void *ptr) 
{
	char *message = (char *)ptr;
	while(1)
	{
		printf("%s\n", message);
		sleep(1);
	}
	pthread_exit(NULL);
}

int main() 
{
	pthread_t thread1, thread2;
	char *message1 = "Thread 1: Hello, world!";
	char *message2 = "Thread 2: Goodbye, world!";

	// 创建线程1
	if (pthread_create(&thread1, NULL, print_message, (void *)message1) != 0) 
	{
		fprintf(stderr, "Error creating thread 1\n");
		return 1;
	}

	// 创建线程2
	if (pthread_create(&thread2, NULL, print_message, (void *)message2) != 0) 
	{
		fprintf(stderr, "Error creating thread 2\n");
		return 1;
	}

	// 等待线程1结束
	if (pthread_join(thread1, NULL) != 0) 
	{
		fprintf(stderr, "Error joining thread 1\n");
		return 1;
	}

	// 等待线程2结束
	if (pthread_join(thread2, NULL) != 0) 
	{
		fprintf(stderr, "Error joining thread 2\n");
		return 1;
	}

	return 0;
}

2. 也可以通过调用 pthread_detach 函数将线程标记为“分离”的。这样,线程在结束时会自动释放资源,而不需要显式地调用 pthread_join 来等待和回收。分离线程一般用于不需要等待其完成的情况,比如后台任务。

调用 pthread_detach 后,即使主线程结束,被分离的线程仍然会继续执行,直到它结束或者被取消。分离状态的线程无法被其他线程等待和回收,因此它们在后台执行,并且不会产生僵尸线程。

需要注意的是,一旦线程被设置为分离状态,就不能再将其恢复为非分离状态。因此,在调用 pthread_detach 之前,需要确保这个线程不再需要被其他线程等待和回收。

但是如果主线程退出了,分离线程也会被终止。整个进程的生命周期是由主线程控制的。当主线程退出时,整个进程会被终止,这也包括所有的分离线程。

分离线程的主要特点是,它们不会阻止进程的终止,即使它们仍在执行。当主线程结束时,系统会自动回收分离线程的资源,并终止它们的执行。因此,分离线程通常用于执行一些不需要主线程等待的后台任务,但在主线程退出时,它们也会被终止。所以使用 pthread_detach 分离线程的一个潜在问题是,如果主线程在分离线程任务开始执行之前就结束了,那么分离线程可能没有机会完成它的任务。这种情况下,分离线程会被立即终止,而不会执行任何任务。

因此,在使用 pthread_detach 分离线程时,需要确保主线程不会过早退出,以允许分离线程有足够的时间来执行其任务。否则,可以考虑其他方式来管理线程,例如使用非分离线程或者适当地设计主线程的生命周期。

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

// 一个简单的线程函数,打印一些文本
void *print_message(void *ptr) 
{
    char *message = (char *)ptr;
    printf("%s\n", message);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;
    char *message1 = "Thread 1: Hello, world!";
    char *message2 = "Thread 2: Goodbye, world!";

    // 创建线程1并设置为分离状态
    if (pthread_create(&thread1, NULL, print_message, (void *)message1) != 0) 
	{
        fprintf(stderr, "Error creating thread 1\n");
        return 1;
    }
    pthread_detach(thread1);

    // 创建线程2并设置为分离状态
    if (pthread_create(&thread2, NULL, print_message, (void *)message2) != 0) 
	{
        fprintf(stderr, "Error creating thread 2\n");
        return 1;
    }
    pthread_detach(thread2);

    // 主线程可以继续执行其他操作,不需要等待分离线程结束

    // 睡眠一段时间以确保分离线程有足够的时间打印信息
    sleep(1);

    return 0;
}

3.pthread_attr_destroy(不常用,看具体场景使用)

使用pthread_attr_destroy 函数来销毁线程属性对象 attr。这个函数的作用是销毁先前通过 pthread_attr_init 初始化的线程属性对象,释放其所占用的资源。

在示例中,我们在创建线程前初始化了一个线程属性对象 attr,然后通过 pthread_attr_setdetachstate 设置了线程的分离状态。在设置完线程属性后,我们调用了 pthread_attr_destroy(&attr) 来销毁这个线程属性对象,因为在后续的代码中不再需要它了。

总的来说,pthread_attr_destroy 函数用于释放不再需要的线程属性对象,以避免资源泄漏。

pthread_attr_destroy 函数通常在需要动态创建和销毁线程属性对象时使用。它的使用频率相对较低,因为大多数情况下,线程属性对象是在函数内部或者全局定义的静态对象,不需要销毁。

int pthread_attr_destroy(pthread_attr_t *attr);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

// 一个简单的线程函数,打印一些文本
void *print_message(void *ptr) 
{
    char *message = (char *)ptr;
    printf("%s\n", message);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;
    char *message1 = "Thread 1: Hello, world!";
    char *message2 = "Thread 2: Goodbye, world!";

    // 创建线程1并设置为分离状态
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (pthread_create(&thread1, &attr, print_message, (void *)message1) != 0) 
	{
        fprintf(stderr, "Error creating thread 1\n");
        return 1;
    }

    // 创建线程2并设置为分离状态
    if (pthread_create(&thread2, &attr, print_message, (void *)message2) != 0) 
	{
        fprintf(stderr, "Error creating thread 2\n");
        return 1;
    }

    // 不再需要 attr,可以销毁
    pthread_attr_destroy(&attr);

    // 主线程可以继续执行其他操作,不需要等待分离线程结束

    // 睡眠一段时间以确保分离线程有足够的时间打印信息
    sleep(1);

    return 0;
}

二、线程安全(同步互斥)

1.使用互斥锁达到互斥

互斥锁(Mutex)是一种用于多线程编程的同步机制,用于保护共享资源免受并发访问的影响。互斥锁可以确保在任何时刻,只有一个线程可以访问被保护的资源,从而避免了竞争条件(Race Condition)的发生。

想象一下你和朋友在玩一个游戏,这个游戏有一把钥匙,你们两个需要轮流使用这把钥匙。问题是,如果你们两个同时试图拿起钥匙,可能会发生什么?或者当一个人正在使用钥匙的时候,另一个人也想拿走钥匙会发生什么?

这个时候,互斥锁就像是一把只有一个人能够拿起的钥匙。当你需要使用这个共享资源(比如说一个全局变量、一个文件等)时,你会先尝试去获取这把互斥锁(也就是拿起钥匙)。如果没有其他人正在使用它,你就可以顺利地获取到互斥锁(拿到钥匙),然后就可以安全地使用这个共享资源了。一旦你使用完毕,你会把互斥锁(钥匙)放回原处,让其他人可以继续使用。

但是,如果另一个人已经拿着这把互斥锁(钥匙),那么你就需要等待,直到他释放出互斥锁。这就保证了在同一时刻,只有一个线程可以访问共享资源,避免了数据的混乱和不一致性。

使用流程:

初始化互斥锁

在使用互斥锁之前,需要先初始化互斥锁。可以使用 pthread_mutex_init 函数进行初始化:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 

加锁(Lock)

在访问共享资源之前,线程需要先尝试获取互斥锁的所有权,这称为加锁。只有成功获取锁的线程才能访问共享资源。可以使用 pthread_mutex_lock 函数来加锁

pthread_mutex_lock(&mutex);
// 访问共享资源
pthread_mutex_unlock(&mutex);

解锁(Unlock)

在线程完成对共享资源的访问后,需要释放互斥锁,以允许其他线程访问共享资源。可以使用 pthread_mutex_unlock 函数来解锁:

pthread_mutex_unlock(&mutex);

销毁互斥锁

在不再需要互斥锁时,需要将其销毁以释放相关资源。可以使用 pthread_mutex_destroy 函数来销毁互斥锁:

pthread_mutex_destroy(&mutex);

注意事项

  • 加锁和解锁必须成对出现,否则可能导致死锁或其他问题。
  • 加锁的线程必须记得在不再需要共享资源时释放锁,否则可能导致其他线程无法访问共享资源。
  • 尽量避免在加锁期间执行耗时操作,以减少锁的持有时间,提高并发性能。

通过合理地使用互斥锁,可以确保多线程程序中的共享资源得到正确地访问和修改,避免了竞争条件和数据不一致的问题。

补充:尝试加锁

如果一个线程尝试获取锁但失败了(因为锁已经被其他线程获取了),它可以选择阻塞等待也可以尝试获取锁并立即返回结果。后者称为尝试加锁,可以使用 pthread_mutex_trylock 函数来尝试加锁。

pthread_mutex_trylock 函数提供了一种非阻塞的方式来尝试获取互斥锁。当线程调用 pthread_mutex_trylock 尝试获取锁时,如果互斥锁当前已经被其他线程占用,那么该函数会立即返回,并返回一个非零值,表示获取锁失败。线程可以根据这个返回值来判断是否获取了锁。

这种方式的优势在于,如果线程在尝试获取锁时不希望被阻塞,可以使用 pthread_mutex_trylock 函数,它会立即返回,线程可以继续执行其他操作。同时,通过不断地尝试获取锁,线程可以在后续的尝试中成功地获取到锁,而不需要一直等待锁的释放。

与之相对应的是上面pthread_mutex_lock 函数,它会在锁被其他线程占用时阻塞当前线程,直到获取到锁为止。pthread_mutex_lock适用于那些必须立即获取锁才能继续执行的情况。

if (pthread_mutex_trylock(&mutex) == 0)

{ // 成功获取锁,访问共享资源

pthread_mutex_unlock(&mutex);

}

else

{ // 锁被其他线程占用,执行其他操作

}

(1)基于join回收的互斥锁

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

#define NUM_THREADS 5

pthread_mutex_t mutex;
int counter = 0;

void *thread_function(void *arg) 
{
    pthread_mutex_lock(&mutex);
    printf("Thread %ld: Counter = %d\n", (long)arg, ++counter);
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

int main() 
{
    pthread_t threads[NUM_THREADS];
    int i;

    pthread_mutex_init(&mutex, NULL);

    for (i = 0; i < NUM_THREADS; i++) 
	{
        if (pthread_create(&threads[i], NULL, thread_function, (void *)i) != 0) 
		{
            fprintf(stderr, "Error creating thread %d\n", i);
            return 1;
        }
    }

    for (i = 0; i < NUM_THREADS; i++) 
	{
        if (pthread_join(threads[i], NULL) != 0) 
		{
            fprintf(stderr, "Error joining thread %d\n", i);
            return 1;
        }
    }

    pthread_mutex_destroy(&mutex);

    return 0;
}

(2)基于使用 pthread_detach 分离回收的互斥锁

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

#define NUM_THREADS 5

pthread_mutex_t mutex;
int counter = 0;

void *thread_function(void *arg) 
{
    pthread_mutex_lock(&mutex);
    printf("Thread %ld: Counter = %d\n", (long)arg, ++counter);
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];
    int i;

    pthread_mutex_init(&mutex, NULL);

    for (i = 0; i < NUM_THREADS; i++) 
	{
        if (pthread_create(&threads[i], NULL, thread_function, (void *)i) != 0) 
		{
            fprintf(stderr, "Error creating thread %d\n", i);
            return 1;
        }
    }

    for (i = 0; i < NUM_THREADS; i++) 
	{
        pthread_detach(threads[i]);
    }

    // 主线程等待一段时间,以确保分离线程有足够的时间来执行
    sleep(1);

    pthread_mutex_destroy(&mutex);

    return 0;
}

2.使用互斥锁和条件变量达到线程间的同步互斥

条件变量是一种用于线程间通信的同步机制,主要用于在线程等待某个条件满足时进行阻塞,以避免忙等待,从而提高系统效率。条件变量通常与互斥锁配合使用,用于保护共享资源的访问,并在共享资源状态发生变化时通知等待的线程。

  1. 避免忙等待: 在某些情况下,线程需要等待某个条件变为真,但不希望采用忙等待的方式不断地检查条件是否满足,以免占用过多的 CPU 资源。条件变量可以让线程在条件不满足时进入阻塞状态,直到条件被满足时才唤醒线程。

  2. 提高系统效率: 使用条件变量可以降低系统资源的消耗,因为线程在等待条件变量时会进入休眠状态,不再占用 CPU 资源,从而提高了系统的整体效率。

下面是一个基于互斥锁和条件变量的多线程示例,其中线程 A 负责生产数据,线程 B 负责消费数据:

在这个示例中,两个线程共享一个缓冲区 bufferproducer 线程负责向缓冲区中生产数据,consumer 线程负责消费数据。通过互斥锁 mutex 和条件变量 cond 来保护缓冲区的访问,当缓冲区为空时,consumer 线程会进入等待状态,直到有数据被生产出来;当缓冲区已满时,producer 线程会进入等待状态,直到有数据被消费掉。

这就是典型的生产者-消费者模型,其中消费者在缓冲区为空时会阻塞等待生产者的通知,而生产者在缓冲区已满时会阻塞等待消费者的通知。这种通过条件变量和互斥锁实现的同步机制,能够确保生产者和消费者之间的协调与同步,有效地避免了竞态条件和数据不一致的问题。

其实只也可以生产者一直生产,生产好了通知消费者,消费者不用通知生产者。消费者可以不需要通知生产者,因为生产者一直在生产数据。在这种情况下,消费者只需要等待生产者通知,当生产者生产好数据时,通知消费者进行消费即可。这种模型更符合实际生产消费的场景,因为生产者负责生产,而消费者负责消费,双方各司其职,相互协作。下面这个例子是使用相互通知的,具体场景需要自己灵活选择

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

#define BUFFER_SIZE 10

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int buffer[BUFFER_SIZE];
int count = 0;

void *producer(void *arg) 
{
    int data = 0;
    while (1) 
	{
        pthread_mutex_lock(&mutex);
        while (count == BUFFER_SIZE) 
		{
            pthread_cond_wait(&cond, &mutex);
        }
        buffer[count++] = data++;
        printf("Produced: %d\n", data);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
        usleep(500000); // 模拟生产数据的耗时操作
    }
    pthread_exit(NULL);
}

void *consumer(void *arg) 
{
    while (1) 
	{
        pthread_mutex_lock(&mutex);
        while (count == 0) 
		{
            pthread_cond_wait(&cond, &mutex);
        }
        int data = buffer[--count];
        printf("Consumed: %d\n", data);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
        usleep(800000); // 模拟消费数据的耗时操作
    }
    pthread_exit(NULL);
}

int main() 
{
    pthread_t producer_thread, consumer_thread;
    pthread_create(&producer_thread, NULL, producer, NULL);
    pthread_create(&consumer_thread, NULL, consumer, NULL);
    pthread_join(producer_thread, NULL);
    pthread_join(consumer_thread, NULL);
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Tkinter组件:Checkbutton

Tkinter组件&#xff1a;Checkbutton Checkbutton&#xff08;多选按钮&#xff09;组件用于实现确定是否选择的按钮。Checkbutton 组件可以包含文本或图像&#xff0c;你可以将一个 Python 的函数或方法与之相关联&#xff0c;当按钮被按下时&#xff0c;对应的函数或方法将被…

【汇编语言小练习输入两个数字然后输出它们的和】

这个程序是用汇编语言编写一个简单的程序&#xff0c;它将从键盘输入两个数字&#xff0c;然后输出它们的和。 .MODEL SMALL .STACK 100H.DATAINPUT_MSG1 DB Enter the first number: $INPUT_MSG2 DB 13, 10, Enter the second number: $RESULT_MSG DB 13, 10, The sum is: $N…

【抽样调查】分层抽样上

碎碎念&#xff1a;在大一大二时听课有的时候会发现听不太懂&#xff0c;那时候只觉得是我自己的基础不好的原因&#xff0c;但现在我发现“听不懂”是能够针对性解决的。比如抽样调查这门课&#xff0c;分析过后我发现我听不懂的原因之一是“没有框架”&#xff0c;一大堆知识…

2024年第六届世界软件工程研讨会(WSSE 2024)即将召开!

2024年第六届世界软件工程研讨会&#xff08;WSSE 2024&#xff09;将于2024年9月13-15日在日本京都举行。软件工程领域的发展离不开各位专家学者和业界精英的共同努力和贡献。WSSE 2024将就软件工程领域的最新研究成果、实践经验和发展趋势进行深入交流和探讨&#xff0c;汇聚…

Ubuntu将软件图标添加到应用列表

一.简介snap snap和yum&#xff0c;apt一样都是安装包工具&#xff0c;但是snap里的软件源是自动更新到最新版本&#xff0c;最好用 比如Ubuntu的软件商城就是使用的snap软件包 二. Ubuntu软件商城更新 1.ps -ef | grep snap-store 查询并kill snap-store的所有进程 2.sudo …

【Linux进程间通信(六)】深入理解 System V IPC

&#xff08;一&#xff09;引入 &#xff08;二&#xff09;IPC 命名空间 &#xff08;三&#xff09;ipc_ips结构体 &#xff08;四&#xff09;ipc_id_ary结构体 &#xff08;五&#xff09;kern_ipc_perm结构体 &#xff08;六&#xff09;操作系统对IPC资源是如何管理…

县供电公司员工向媒体投稿发文章用亲身经历告诉你并不难

在县供电公司的日子里,我肩负着一项至关重要的使命——信息宣传工作。这不仅仅是一份职责,更是连接公司与外界的桥梁,通过新闻稿件传递我们的声音,展示我们的成果。然而,回忆起刚刚踏入这个领域的时光,那段经历至今让我感慨万千。 初涉投稿,步履维艰 刚接手这项工作时,我的投稿…

探索DeepSeek平台:新一代MoE模型的深度体验

简介 DeepSeek是一个创新的人工智能平台&#xff0c;它最近推出了其最新版本的模型——DeepSeek-V2 MoE&#xff08;Mixture of Experts&#xff09;。这个平台不仅提供了一个交互式的聊天界面&#xff0c;还提供了API接口&#xff0c;让用户可以更深入地体验和利用这一先进的…

全体模型师请做好日入过万的准备!3D模型库海量资源,老子云平台免费用

在数字化的大背景下&#xff0c;3D转型已然成为了多行业关注的重点战略版块。无论是科技、金融、互联网、化工、建筑等等各个行业都在加速布局&#xff0c;3D手段会成为下一个重要的技术风口。也正因如此&#xff0c;3D市场潜能巨大&#xff0c;并且3D需求每年都在暴涨&#xf…

3d中如何对模型粉碎处理?---模大狮模型网

在3D建模和动画设计中&#xff0c;模型粉碎处理是一种引人注目的效果&#xff0c;可以为场景增添动态和震撼的视觉效果。无论是用于电影特效、游戏设计还是虚拟现实项目&#xff0c;都可以通过模型粉碎处理来创造出引人入胜的场景。本文将介绍如何在3D中轻松实现模型粉碎处理&a…

本地连接服务器Jupyter【简略版】

首先需要在你的服务器激活conda虚拟环境&#xff1a; 进入虚拟环境后使用conda install jupyter命令安装jupyter&#xff1a; 安装成功后先不要着急打开&#xff0c;因为需要设置密码&#xff0c;使用jupyter notebook password命令输入自己进入jupyter的密码&#xff1a; …

Windows端之Python3.9及以上高版本工程打包得到的exe逆向工程解包得到pyc文件进而得到py文件的流程实现

参考来自 【python逆向 pyc反编译】python逆向全版本通杀_python反编译pyc-CSDN博客https://blog.csdn.net/zjjcxy_long/article/details/127346296Pyinstaller打包的exe之一键反编译py脚本与防反编译_pyinstaller防止反编译-CSDN博客https://blog.csdn.net/as604049322/artic…

「网络流 24 题」魔术球 【最小路径覆盖】

「网络流 24 题」魔术球 注意这里的球是依次放置&#xff0c;也就是说如果当前放到第 i i i 号球&#xff0c;那么 1 → i − 1 1 \rarr i - 1 1→i−1 号球都已经放好了&#xff0c;否则可以放无数个球 思路 首先我们对于 i < j 且 i j 完全平方数 i < j 且 i j…

在思科和华为上实现两个主机A,B A能ping通B,B不能ping通A

1.华为实验的topo如下 常规状态下任意两台主机都是可以ping通的 此时的需求是PC4能ping通PC2和PC3但是PC2和PC3不能ping通PC4 这里需要用到ACL策略 在接口上调用 验证&#xff1a; PC4能ping通PC2和PC3 PC2和PC3不能ping通PC4 2.思科类似 正常情况下是都能互相ping通 加上ac…

嵌入式Linux的QT项目CMake工程模板分享及使用指南

在嵌入式linux开发板上跑QT应用&#xff0c;不同于PC上的开发过程。最大的区别就是需要交叉编译&#xff0c;才能在板子上运行。 这里总结下嵌入式linux环境下使用CMake&#xff0c;嵌入式QT的CMake工程模板配置及如何使用&#xff0c;分享给有需要的小伙伴&#xff0c;有用到的…

Github的使用教程(下载和上传项目)

根据『教程』一看就懂&#xff01;Github基础教程_哔哩哔哩_bilibili 整理。 1.项目下载 1&#xff09;直接登录到源码链接页或者通过如下图的搜索 通过编程语言对搜索结果进一步筛选。 2&#xff09;红框区为项目的源代码&#xff0c;README.md &#xff08;markdown格式&…

企业如何用数字化为预提摊销业务赋能?

对于企业来说&#xff0c;想要实现系统化、智能化、自动化的预提摊销管理&#xff0c;需要做足哪些功课&#xff1f;常见场景下的业务难题又该如何破解&#xff1f;今天胜意科技就给大家介绍一下&#xff0c;企业如何通过数字化手段搞定预提摊销业务难题。 一、预提摊销痛点 在…

Spring后端参数校验——自定义校验方式(validation)

文章目录 开发场景技术名词解释——Spring Validation自定义校验 技术细节小结1.实体参数校验2.自定义校验 完整代码 开发场景 业务场景&#xff1a;新增文章 基本信息 请求路径&#xff1a;/article 请求方式&#xff1a;POST 接口描述&#xff1a;该接口用于新增文章(发布文…

小样本学习

小样本学习的概念最早从计算机视觉(computer vision)[8]领域兴起, 近几年受到广泛关注, 在图像分类任务中已有很多性能优异的算法模型[9-11].但是在自然语言处理领域(natural language processing)[12]的发展较为缓慢, 原因在于图像和语言特性不同.图像相比文本更为客观, 所以当…

学习方法的重要性

原贴&#xff1a;https://www.cnblogs.com/feily/p/13999204.html 原贴&#xff1a;https://36kr.com/p/1236733055209095 1、 “一万小时定律”的正确和误区 正确&#xff1a; 天才和大师的非凡&#xff0c;不是真的天资超人一等&#xff0c;而是付出了持续不断的努力&…
最新文章