头歌 计算机操作系统 Linux之线程同步二

news2024/12/13 1:22:07

第1关:信号量

任务描述


在上一个实训中,我们学习了使用互斥锁来实现线程的同步,Linux系统中还提供了另一个类似互斥锁的线程不同操作,那就是信号量。

本关任务:学会使用信号量来实现线程间的同步与互斥。

相关知识


互斥锁变量(Mutex)是非0即1的,可看作一种资源的可用数量。当初始化Mutex为1时,则表示当前资源可用,可以通过加锁操作来获取该资源,当加锁成功后,将Mutex减到0。当Mutex为0时,则表示当前资源不可用,只有对该资源进行减锁操作后,该资源才可用,当减锁成功后,将Mutex重新加到1。

Linux系统中提供与互斥锁相似功能的操作,它就是信号量。它们都可以用来表示资源的可用数量,与互斥锁不同之处是,信号量可以表示资源的可用数量大于1,而互斥锁只能是1。

信号量广泛用于线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当信号量值大于0时,则可以访问,否则将阻塞。PV 原语是对信号量的操作,一次P操作使信号量减1,一次V操作使信号量加1。

信号量用于多线程同步的步骤如下所示:

[信号量同步多线程]

以上操作可以保证,线程1和线程2的执行顺序为:线程1 >  线程2 > 线程1 > 线程2> ...。这样就实现了线程的同步执行。

信号量用于多线程互斥的步骤如下所示:

[信号量互斥多线程]

以上操作可以保证,线程1和线程2同一时刻只能有一个线程执行。这样就实现了线程的互斥执行。

Linux 系统中提供了如下几个函数来操作信号量:

以上函数我们可以使用man命令来查询该函数的使用方法。具体的查询命令为:man 3 函数名。

初始化信号量


Linux 系统提供一个sem_init库函数来对信号量进行初始化。

sem_init函数的具体的说明如下:

需要的头文件如下:

#include <semaphore.h>

函数格式如下:

int sem_init(sem_t *sem, int pshared, unsigned int value);

参数说明:

sem:信号量变量;
pshared:是否共享,如果的值为0,那么信号量将被进程内的线程共享。如果是非零值,那么信号量将在进程之间共享。
value:信号量的初始值;

函数返回值说明:
调用成功,返回值为0,否则返回值为-1,并且设置错误代码errno。

P操作


判断资源使用可用,则使用信号量P操作,也就是当信号量值大于零时,P操作将信号量值减一并返回,如果信号量值小于等于零,则P操作阻塞,Linux提供两个常见的P操作函数,分别是:sem_wait和sem_trywait,这些函数的具体的说明如下:

需要的头文件如下:

#include <semaphore.h>


函数格式如下:

int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);


参数说明:

sem:要被执行P操作的信号量变量

函数返回值说明:
调用成功,返回值为0,否则返回值为-1,并且设置错误代码errno。

sem_wait和sem_trywait区别:
用sem_wait执行P操作时,如果sem的值等于0,则当前线程被阻塞等待。而sem_trywait函数则不同,如果sem的值等于0,它将立即返回而不是阻塞等待,并且设置错误代码为EAGAIN。

V操作


对信号量有减一操作(P操作),则就存在响应的加一操作(V操作)。Linux提供了一个sem_post函数来执行V操作,这个函数的具体的说明如下:

需要的头文件如下:

#include <semaphore.h>


函数格式如下:

int sem_post(sem_t *sem);


参数说明:

sem:要被执行V操作的信号量变量

函数返回值说明:
调用成功,返回值为0,否则返回值为-1,并且设置错误代码errno。

获取信号量值操作


Linux 提供了一个sem_getvalue函数来获取信号量值操作,这个函数的具体的说明如下:

需要的头文件如下:

#include <semaphore.h>


函数格式如下:

int sem_getvalue(sem_t *sem, int *sval);


参数说明:

sem:要获取值的信号量变量;
sval:用于存放信号量的值;


函数返回值说明:
调用成功,返回值为0,否则返回值为-1,并且设置错误代码errno。

注销信号量操作


当一个信号量使用完毕后,必须进行清除。Linux 提供了一个sem_destroy函数来注销一个信号量,这个函数的具体的说明如下:

需要的头文件如下:

#include <semaphore.h>


函数格式如下:

int sem_destroy(sem_t *sem);


参数说明:

sem:要被执行注销操作的信号量

函数返回值说明:
调用成功,返回值为0,否则返回值为-1,并且设置错误代码errno。

案例演示1:


编写一个程序,使用信号量来使得线程互斥执行。详细代码如下所示:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
char *buffer[2];
int position = 0;
//定义一个全局的信号量
sem_t sem;
void *addNumer(void *arg)
{
    sem_wait(&sem);
    buffer[position] = (char *)arg;
    sleep(1);
    position++;
    sem_post(&sem);
    
    return NULL;
}
int main()
{
    sem_init(&sem, 0, 1);   //初始化信号量为1
    int i;
    for(i = 0; i < 2; i++)
        buffer[i] = NULL;
    
    pthread_t thread1, thread2;
    pthread_create(&thread1, NULL, addNumer, "String1");
    pthread_create(&thread2, NULL, addNumer, "String2");
    
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    
    
    for(i = 0; i < 2; i++)
    {
        if(buffer[i] != NULL);
            printf("%s\n", buffer[i]);
    }
    
    sem_destroy(&sem);   //注销信号量
    
    return 0;
}

将以上代码保存为semThread.c文件,编译执行。可以看到buffer数组中每个元素(buffer[0]和buffer[1])都不为空,如果我们没有使用信号量来互斥线程,则可能出现buffer数组中只有一个元素(buffer[0])不为空,而另一个元素(buffer[1])为空。

编程要求


本关的编程任务是补全右侧代码片段中Begin至End中间的代码,具体要求如下:

补全ThreadHandler1和ThreadHandler2函数中代码,使用信号量来同步这两个线程(两个线程相互交替执行),使其执行顺序为ThreadHandler1 > ThreadHandler2 > ThreadHandler1...;
信号量sem1被初始化为1,信号量sem2被初始化为0;
提示:参考相关知识中的信号量同步多线程内容;


测试说明


本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。

开始你的任务吧,祝你成功!

解答:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
 
//全局信号量  sem1已被初始化为1,sem2被初始化为0
extern sem_t sem1, sem2;
 
//全局共享变量
extern char *ch;
 
/************************
 * 参数arg: 是线程函数的参数
*************************/
void *ThreadHandler1(void *arg)
{
	int i = 0;
	for(i = 0; i < 3; i++)
	{
		/********** BEGIN **********/
		sem_wait(&sem1);
		/********** END **********/
		printf("%c", *ch);
		usleep(100);
		ch++;
		
		/********** BEGIN **********/
		sem_post(&sem1);
		/********** END **********/
	}
	
	pthread_exit(NULL);
}
 
/************************
 * 参数arg: 是线程函数的参数
*************************/
void *ThreadHandler2(void *arg)
{
	int i = 0;
	for(i = 0; i < 3; i++)
	{
		/********** BEGIN **********/
		sem_wait(&sem1);
		/********** END **********/
		printf("%c", *ch);
		ch++;
		
		/********** BEGIN **********/
		sem_post(&sem1);
		/********** END **********/
	}
	
	pthread_exit(NULL);
}

第2关:读写锁

任务描述


当有一个数据即可以被读取,又可以被修改时,为了保证数据的一致性,最简单的方法是通过互斥锁对数据进行加锁操作。但是互斥锁的缺点是一旦数据被加锁后,只能有一个读线程或写线程来执行,而我们实际想要的效果是,同一时刻只能有一个线程对数据进行修改,而同一时刻可以有多个线程对其进行读取操作。那么互斥锁就无法满足我们的需求。Linux 系统中存在另一个锁可以实现以上需求,那就是读写锁。

本关任务:学会使用读写锁来实现线程间的同步。

相关知识


读写锁与互斥量类似,不过读写锁允许更改的并行性。互斥量要么是锁住状态,要么就是不加锁状态,而且一次只有一个线程可以对其加锁。因此,读写锁允许更高的并行性。

读写锁对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。读写锁允许同时有多个读者来访问共享资源,而只允许同时有一个写则来访问共享资源,并且读写锁同时只能有一个写者或多个读者来访问共享资源。因此,读写锁具有的 写独占 和 读共享 的特性。

读写锁的规则如下所示:

如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁;
如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁;
Linux 系统中提供了如下几个函数来操作读写锁:

以上函数我们可以使用man命令来查询该函数的使用方法。具体的查询命令为:man 3 函数名。

初始化读写锁


使用读写锁前必须先进行初始化操作。在 Linux 中初始化读写锁有两种方式,分别是:(1)静态赋值法;(2)使用初始化函数。

1、静态赋值法
静态赋值法是直接将宏结构常量直接赋值给互斥锁,例如使用静态赋值法来初始化一个读写锁变量:

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

2、函数赋值法
Linux 系统提供一个pthread_rwlock_init库函数来对读写锁进行初始化。
pthread_rwlock_init函数的具体的说明如下:

需要的头文件如下:

#include <pthread.h>


函数格式如下:

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


参数说明:

rwlock:读写锁变量;
attr:读写锁属性,通常设置为NULL;


函数返回值说明:
调用成功,返回值为0,否则返回值为非零的错误代码。

加锁操作


读写锁的加锁操作分为两个,分别是:读加锁和写加锁。对于读加锁 Linux 提供了两个库函数,分别是:pthread_rwlock_rdlock和pthread_rwlock_tryrdlock。对于写加锁 Linux 提供了两个库函数,分别是:pthread_rwlock_wrlock和pthread_rwlock_trywrlock。这些函数的具体的说明如下:

需要的头文件如下:

#include <pthread.h>


函数格式如下:

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


参数说明:

rwlock:要被执行加锁操作的读写锁变量

函数返回值说明:
调用成功,返回值为0,否则返回一个非零的错误码。

pthread_rwlock_rdlock和pthread_rwlock_tryrdlock区别:
用pthread_rwlock_rdlock加锁时,如果rwlock已经被写线程所锁住,当前尝试加读锁的线程就会被阻塞,直到写线程将rwlock释放。而pthread_rwlock_tryrdlock函数则不同,如果rwlock已经被写线程所锁住,它将立即返回,返回的错误码为EBUSY,而不是阻塞等待。

pthread_rwlock_wrlock和pthread_rwlock_trywrlock区别:
用pthread_rwlock_wrlock加锁时,如果rwlock已经被读线程所锁住,当前尝试加写锁的线程就会被阻塞,直到读线程将rwlock释放。而pthread_rwlock_trywrlock函数则不同,如果rwlock已经被读线程所锁住,它将立即返回,返回的错误码为EBUSY,而不是阻塞等待。

解锁操作


有加锁操作就相对应的有解锁操作。Linux 提供了一个pthread_rwlock_unlock函数来解锁操作,包括了解读锁和解写锁,这个函数的具体的说明如下:

需要的头文件如下:

#include <pthread.h>


函数格式如下:

 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);


参数说明:

rwlock:要被执行解锁操作的读写锁变量

函数返回值说明: 调用成功,返回值为0,否则返回一个非零的错误码。


注销锁操作


当一个读写锁使用完毕后,必须进行清除。Linux 提供了一个pthread_rwlock_destroy函数来注销一个读写锁,这个函数的具体的说明如下:

需要的头文件如下:

#include <pthread.h>


函数格式如下:

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);


参数说明:

rwlock:要被执行注销操作的读写变量

函数返回值说明:
调用成功,返回值为0,否则返回一个非零的错误码。

注意:如果使用静态初始化来初始化一个读写锁,则无需使用pthread_rwlock_destroy对其注销。

案例演示1:


编写一个程序,使用静态初始化方法来初始化一个互斥锁,并对一个全局变量进行加锁。详细代码如下所示:

#include <stdio.h>
#include <pthread.h>
#include <stddef.h>
#include <time.h>
int globalNumber = 2;
//定义一个读写锁
pthread_rwlock_t numberRWlock;
void *readNumber(void *arg)
{
    int i = 0;
    for(i = 0; i < 3; i++)
    {
        pthread_rwlock_rdlock(&numberRWlock);
        time_t timer;
        struct tm *tblock;
        timer = time(NULL);
        tblock = localtime(&timer);
        printf("globalNumber: %d\tcurrent time: %s", globalNumber, asctime(tblock));
        pthread_rwlock_unlock(&numberRWlock);
        sleep(2);
    }
    
    return NULL;
}
void *writeNumber(void *arg)
{
    int i = 0;
    for(i = 0; i < 6; i++)
    {
        pthread_rwlock_wrlock(&numberRWlock);
        globalNumber++;
        pthread_rwlock_unlock(&numberRWlock);
        sleep(1);
    }
    
    return NULL;
}
int main()
{
    //初始化读写锁
    pthread_rwlock_init(&numberRWlock, NULL);
    
    pthread_t thread1, thread2, thread3;
    pthread_create(&thread1, NULL, readNumber, NULL);
    pthread_create(&thread2, NULL, readNumber, NULL);
    pthread_create(&thread3, NULL, writeNumber, NULL);
    
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    pthread_join(thread3, NULL);
    //注销读写锁
    pthread_rwlock_destroy(&numberRWlock);
    
    return 0;
}

将以上代码保存为RWLockThread.c文件,编译执行。可以看到读数据的两个线程是同时将globalNumber变量的值打印出来,也就是说读线程是同时执行的。

编程要求


本关的编程任务是补全右侧代码片段中Begin至End中间的代码,具体要求如下:

补全ReadHandler和WriteHandler函数中代码,使用读写锁对position和buffer变量加锁;
使同一时刻只能有一个线程执行WriteHandler函数,并且没有线程执行ReadHandler函数;
当没有线程执行WriteHandler函数时,允许有多个线程同时执行ReadHandler函数。
测试用例:存在3个线程来执行WriteHandler函数,存在2个线程来执行ReadHandler函数;
提示:执行WriteHandler函数的线程优先级高于执行ReadHandler函数的线程,并且buffer变量的默认为a;


测试说明


本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。

开始你的任务吧,祝你成功!

解答:

#include <stdio.h>
#include <pthread.h>
 
//全局读写锁
extern pthread_rwlock_t rwlock;
 
//全局共享变量
extern char buffer[3];
extern int position;
 
/************************
 * 参数arg: 是线程函数的参数
*************************/
void *ReadHandler(void *arg)
{
	int i;
	for(i = 0; i < 3; i++)
	{
		/********** BEGIN **********/
		pthread_rwlock_rdlock(&rwlock);
		/********** END **********/
		printf("%c\n", buffer[i]);
		
		/********** BEGIN **********/
		pthread_rwlock_unlock(&rwlock);
		/********** END **********/
		
		usleep(800);
	}
	
 
	pthread_exit(NULL);
}
 
/************************
 * 参数arg: 是线程函数的参数
*************************/
void *WriteHandler(void *arg)
{
	/********** BEGIN **********/
	pthread_rwlock_wrlock(&rwlock);
	/********** END **********/
 
	buffer[position] = *(char*)arg;
	sleep(1);
	position++;
	
	/********** BEGIN **********/
	pthread_rwlock_unlock(&rwlock);
	/********** END **********/
 
	pthread_exit(NULL);
}

第3关:项目实战

任务描述


本关任务:利用信号量实现一个读写锁。

相关知识


Linux 系统中提供了现成的读写锁库函数,也就是上一关我们学习的。其实,我们利用其他线程同步的方法也可以实现一个读写锁。

读写锁 需要满足如下规则:

如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁;
如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁;
通过实训"Linux之线程同步一"的学习,我们现在知道如何互斥锁和条件变量来同步线程。那么利用 互斥锁 和 条件变量 知识就可以简单的读写锁。

利用条件变量和互斥锁实现读写锁


使用条件变量和互斥锁实现读写锁,根据读写锁的特性,当有读者在读取数据时,则不能有线程对数据进行写操作,并且同时可以存在多个读者。当有写者在对数据进行写操作的时候,则不能有线程对数据进行读操作,并且同一时刻只能有一个写者。因此,实现一个简单的读写锁可以分为以下几步:

定义两个变量用于记录读者(readNum)和写者(writeNum)的个数;
对于写模式的加锁,如果readNum和writeNum同时为0,则将writeNum设置为1表示此时有一个写者需要对数据进行写操作;否则,写模式的加锁操作处于等待状态;
对于写模式的解锁,如果完成的写操作,此时需要将writeNum设置为0,表示此时没有写操作,并且通知读者可以读取数据了;
对于读模式的加锁,如果writeNum为0,则表示当前没有写操作,可以读取数据,并且将readNum值加一表示多了一个读者;否则,读模式的加锁操作处于等待状态;
对于读模式的解锁,如果完成的读操作,此时需要将readNum减一操作,然后判断readNum是否为0,如果为零,则通知写者可以执行写操作;

详细的代码设计为:

//定义两个变量用于标示读者和写者的个数
int readNum, writeNum;
//定义一个条件变量和互斥变量
pthread_cond_t cond;
pthread_mutex_t mutex;   //用于同步readNum和writeNum两个变量
//初始化函数
void my_rwlock_init()
{
    readNum = writeNum = 0;
    pthread_cond_init(&cond, NULL);
    pthread_mutex_init(&mutex, NULL);
}
//注销函数
void my_rwlock_destroy()
{
    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);
}
//写模式加锁
void my_rwlock_wrlock()
{
    pthread_mutex_lock(&mutex);
    while(readNum > 0 || writeNum != 0)
        pthread_cond_wait(&cond, &mutex);
    writeNum = 1;
    pthread_mutex_unlock(&mutex);
}
//写模式解锁
void my_rwlock_unwrlock()
{
    pthread_mutex_lock(&mutex);
    writeNum = 0;
    //写者完成了写操作后,则通知读者可以读取数据
    pthread_cond_broadcast(&cond);
    pthread_mutex_unlock(&mutex);
}
//读模式加锁
void my_rwlock_rdlock()
{
    pthread_mutex_lock(&mutex);
    while(writeNum != 0)
        pthread_cond_wait(&cond, &mutex);   //当存在写者进行写操作时,则睡眠当前读者线程
    readNum++;
    pthread_mutex_unlock(&mutex);
}
//读模式解锁
void my_rwlock_unrdlock()
{
    pthread_mutex_lock(&mutex);
    readNum--;
    if(readNum == 0)
        pthread_cond_broadcast(&cond);   //当读者数量为0时,则通知写者可以进行写操作
    pthread_mutex_unlock(&mutex);
}

利用互斥锁实现读写锁


只使用互斥锁也可以实现读写锁,详细的步骤可分为以下几步:

定义一个变量用于记录读者(readNum)的个数和两个互斥锁,分别是读模式的互斥锁(mutex_read)和写模式的互斥锁(mutex_write);
对于写模式的加锁,直接对mutex_write进行加锁操作即可;
对于写模式的解锁,直接对mutex_write进行解锁操作即可;
对于读模式的加锁,首先判断读者的数量是否为0,如果为0,则表示第一个读者要去读取数据,那么此时要禁止写者进行写数据操作,所以对mutex_write进行加锁操作并设置readNum++;否则直接将readNum++即可;
对于读模式的解锁,首先将readNum--,然后判断此时是否readNum为0,如果为0,则表示现在允许写者可以写数据,因此要对mutex_write进行解锁操作;

详细的代码设计为:

//定义一个变量用于标示读者的个数
int readNum;
//定义两个互斥变量
pthread_mutex_t mutex_read; 
pthread_mutex_t mutex_write;
//初始化函数
void my_rwlock_init()
{
    readNum = 0;
    pthread_mutex_init(&mutex_read, NULL);
    pthread_mutex_init(&mutex_write, NULL);
}
//注销函数
void my_rwlock_destroy()
{
    pthread_mutex_destroy(&mutex_write);
    pthread_mutex_destroy(&mutex_read);
}
//写模式加锁
void my_rwlock_wrlock()
{
    pthread_mutex_lock(&mutex_write);
}
//写模式解锁
void my_rwlock_unwrlock()
{
    pthread_mutex_unlock(&mutex_write);
}
//读模式加锁
void my_rwlock_rdlock()
{
    pthread_mutex_lock(&mutex_read);
    if(readNum == 0)
        pthread_mutex_lock(&mutex_write);  //表示第一个读者要去读取数据,那么此时要禁止写者进行写数据操作
    readNum++;
    pthread_mutex_unlock(&mutex_read);
}
//读模式解锁
void my_rwlock_unrdlock()
{
    pthread_mutex_lock(&mutex_read);
    readNum--;
    if(readNum == 0)
        pthread_mutex_unlock(&mutex_write);   //表示现在允许写者可以写数据
    pthread_mutex_unlock(&mutex_read);
}

编程要求


本关的编程任务是补全右侧代码片段中Begin至End中间的代码,具体要求如下:

利用信号量实现读写锁功能;
补全sem_rwlock_rdlock和sem_rwlock_unrdlock函数;
sem_rwlock_rdlock函数用于读模式下的读加锁操作;
sem_rwlock_unrdlock函数用于读模式下的读解锁操作;
提示:参考两个互斥锁和一个变量实现读写锁的方式,互斥锁其实就是 0-1 信号量;
评测读写锁实现是否正确所使用的测试用例与上一关测试用例一致,详细描述参考上一关编程要求介绍;


测试说明


本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。

开始你的任务吧,祝你成功!

解答:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
 
//记录读线程的个数
extern int reader;
 
//全局的信号量变量
extern sem_t sem_read, sem_write;
 
//读写锁初始化函数
void sem_rwlock_init()
{
	reader = 0;
	//初始化信号量个1
	sem_init(&sem_read, 0, 1);
	sem_init(&sem_write, 0, 1);
}
 
//读写锁注销函数
void sem_rwlock_destroy()
{
	sem_destroy(&sem_read);
	sem_destroy(&sem_write);
}
 
//读模式下的加锁操作
void sem_rwlock_rdlock()
{
	//读模式下加锁操作
	/********** BEGIN **********/
	sem_wait(&sem_read);
    if(reader == 0)
        sem_wait(&sem_write); 
    reader++;
    sem_post(&sem_read);
	/********** END **********/
}
 
//读模式下的解锁操作
void sem_rwlock_unrdlock()
{
	//读模式下解锁操作
	/********** BEGIN **********/
    sem_wait(&sem_read);
    reader--;
    if(reader == 0)
       sem_post(&sem_write);
	sem_post(&sem_read);
	/********** END **********/
}
 
//写模式下的加锁操作
void sem_rwlock_wrlock()
{
	sem_wait(&sem_write);
}
 
//写模式下的解锁操作
void sem_rwlock_unwrlock()
{
	sem_post(&sem_write);
}

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

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

相关文章

基于MinIO打造高可靠分布式“本地”文件系统

MinIO是一款高性能的对象存储服务&#xff0c;而S3协议是由亚马逊Web服务&#xff08;AWS&#xff09;制定的一种标准协议&#xff0c;用于云存储服务之间的数据交换。MinIO与S3协议的关系在于&#xff0c;MinIO实现了S3协议的接口&#xff0c;这意味着用户可以使用与AWS S3相同…

【MIT-OS6.S081作业1.3】Lab1-utilities primes

本文记录MIT-OS6.S081 Lab1 utilities 的primes函数的实现过程 文章目录 1. 作业要求primes (moderate)/(hard) 2. 实现过程2.1 代码实现 1. 作业要求 primes (moderate)/(hard) Write a concurrent version of prime sieve using pipes. This idea is due to Doug McIlroy, in…

Js如和返回数组中的指定列

一、需求 日常工作中需要返回数组中的指定列&#xff0c;例如Echarts 和 下拉框 选择 id&#xff0c;value 类似这种都需要在数组中提取指定列元素。 二、代码示例 const products [{ name: "商品1", price: 100, inventory: 50 },{ name: "商品2", pri…

C++的一些经典算法

以下是C的一些经典算法&#xff1a; 一、排序算法 冒泡排序&#xff08;Bubble Sort&#xff09; 原理&#xff1a; 它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换…

35.1 thanos项目介绍和二进制部署

本节重点介绍 : 核心优点 无需维护存储&#xff0c;存储高可用&#xff1a; 利用廉价的公有云对象存储&#xff0c;高可用长时间存储&#xff0c;数据降采样&#xff1a;利用Compactor降采样完全适配原生prometheus查询接口&#xff1a;Query实现多级数据缓存配置 二进制部署 …

【PlantUML系列】状态图(六)

一、状态图的组成部分 状态&#xff1a;对象在其生命周期内可能处于的条件或情形&#xff0c;使用 state "State Name" as Statename 表示。初始状态&#xff1a;表示对象生命周期的开始&#xff0c;使用 [*] 表示。最终状态&#xff1a;表示对象生命周期的结束&…

Android 15(V)新功能适配,雕琢移动细节之美

Android 15&#xff0c;内部代号为Vanilla Ice Cream&#xff0c;是Android移动操作系统的最新主要版本&#xff0c;于2024年2月16日在开发者预览版1中发布。Android 15源代码于 2024年9月4日发布。Android 15稳定版于2024年10月15日发布。 以下是针对 Android 15&#xff08;…

【零成本抽象】基本概念与在C++中的实现

零成本抽象概念是由 Bjarne Stroustrup 提出的,他在 1994 年的著作中就有相关设想,2016 年其在 C++ 大会登台演讲时,明确阐述了 C++ 中的 “零成本抽象” 这一理念。 一、零成本抽象概念 Bjarne Stroustrup提出的零成本抽象概念,是指在编程中使用高级抽象机制时,不会产生…

android编译assets集成某文件太大更新导致git仓库变大

不知道大家有没有类似的困扰&#xff0c;你的工程assets文件过大&#xff0c;我曾经在某度车机地图团队工作过一段时间时候&#xff0c;每次发包会集成一个上百MB的文件。工作一段时间你的git仓库将会增加特别多。最后&#xff0c;你会发现你如果重新git clone这个仓库会非常大…

F5-TTS文本语音合成模型的使用和接口封装

F5-TTS文本语音生成模型 1. F5-TTS的简介 2024年10月8日&#xff0c;上海交通大学团队发布&#xff0c;F5-TTS (A Fairytaler that Fakes Fluent and Faithful Speech with Flow Matching) 是一款基于扩散Transformer和ConvNeXt V2的文本转语音 (TTS) 模型。F5-TTS旨在生成流…

克隆选择算法复现

克隆选择算法复现 基于克隆选择算法求解0 - 1背包问题的代码复现文档一、背景和意义&#xff08;一&#xff09;背景&#xff08;二&#xff09;意义 二、算法原理&#xff08;一&#xff09;克隆选择算法基础&#xff08;二&#xff09;受体编辑机制 三、算法流程&#xff08;…

Scala的隐式对象

Scala中&#xff0c;隐式对象&#xff08;implicit object&#xff09;是一种特殊的对象&#xff0c;它可以使得其成员&#xff08;如方法和值&#xff09;在特定的上下文中自动可用&#xff0c;而无需显式地传递它们。隐式对象通常与隐式参数和隐式转换一起使用&#xff0c;以…

观察者模式的理解和实践

引言 在软件开发中&#xff0c;设计模式是开发者们为了解决常见的设计问题而总结出来的一系列最佳实践。观察者模式&#xff08;Observer Pattern&#xff09;是其中一种非常经典且使用率极高的设计模式。它主要用于定义对象之间的一对多关系&#xff0c;使得当一个对象的状态发…

windows下Qt5自动编译配置QtMqtt环境(11)

文章目录 [toc]1、概述2、准备1.1 下载源码1.2 配置环境1.3 解释原理 3、编译4、验证5、参考6、视频 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;Qt网络编程 &#x1f448; 1、概述 Qt默认是不包含mqtt库的&#xff0c;如果需要使用到mqtt库就只能自己编译配…

【6】数据分析检测(DataFrame 1)

学习目标3 昨天&#xff0c;我们学习了Series。 而Pandas的另一种数据类型&#xff1a;DataFrame&#xff0c;在许多特性上和Series有相似之处。 今天&#xff0c;我们将学习DataFrame的相关知识&#xff1a; 1. DataFrame的概念 2. 构造一个DataFrame 3. DataFrame的常用…

如何选择安全、可验证的技术?

澳大利亚信号局的澳大利亚网络安全中心 (ASD 的 ACSC) 发布了一份指导文件&#xff0c;题为《选择安全和可验证的技术》&#xff0c;旨在帮助组织在采购软件&#xff08;专有或开源&#xff09;、硬件&#xff08;例如物联网设备&#xff09;和云服务&#xff08;SaaS、MSP 服务…

趣味编程:猜拳小游戏

1.简介 这个系列的第一篇以猜拳小游戏开始&#xff0c;这是源于我们生活的灵感&#xff0c;在忙碌的时代中&#xff0c;我们每个人都在为自己的生活各自忙碌着&#xff0c;奔赴着自己所走向的那条路上&#xff0c;即使遍体鳞伤。 但是&#xff0c;生活虽然很苦&#xff0c;也不…

轮转数组

轮转数组 1、题目描述2、解答思路2.1、辅助数组2.2、原地反转 1、题目描述 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 2、解答思路 2.1、辅助数组 如果我们在原数组上通过覆盖元素会导致部分元素的丢失&#xff0c…

如何编译安装系统settings设置应用(5.0.0-Release)

本文介绍如何在OpenHarmony 5.0.0 r版本中修改系统设置应用&#xff0c;并且编译安装到开发板上 开发环境 1.dayu200开发板 2.OpenHarmony 5.0.0r 固件 3.API12 full sdk &#xff08;如果安装full sdk过程中出现报错hvigor ERROR: Cannot find module typescript,请参考 h…

学习记录,隐式对象,隐式类

隐式对象 格式&#xff1a;就是在对象前面加一个 implicit 作用&#xff1a;就是给函数当默认值&#xff01; 隐式类 隐式类 一个类 一个隐式转换函数 格式&#xff1a;在class 的前面&#xff0c;添加implicit 要点&#xff1a;要有一个参数&#xff0c;就要待被转换的类型…