嵌入式Linux学习笔记(6)-线程处理、线程同步、线程池(c语言实现)

一、概述

        线程是一种轻量级的并发执行的机制。线程是进程中的一个实体,它执行在同一进程的上下文中,共享同一内存空间,但拥有独立的栈空间。

        C语言的线程使用pthread库实现,通过包含头文件 pthread.h 来使用相关的函数和数据类型

二、线程处理

1、相关函数

        以下是一些常用的线程处理函数及其用法:

        pthread_create:用于创建一个新的线程。原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

        参数:

        thread:指向线程标识符的指针,用于保存新创建线程的ID。

        attr:用于指定线程属性的对象。通常使用默认属性,可以传递NULL。

        start_routine:指向线程函数的指针。

        arg:传递给线程函数的参数。

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

        pthread_exit:用于终止当前线程并返回一个值。原型:

void pthread_exit(void *value_ptr);

        参数:

        value_ptr:指向线程返回值的指针。

        返回值:无。

        pthread_join:用于等待指定的线程终止。原型:

int pthread_join(pthread_t thread, void **value_ptr);

        参数:

        thread:要等待的线程的ID。

        value_ptr:指向线程返回值的指针。

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

        pthread_detach:将线程标记为分离状态,使得线程在退出时自动释放资源,无需其他线程调用pthread_join进行等待。原型:

int pthread_detach(pthread_t thread);

        参数:

        thread:要分离的线程的ID。

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

        pthread_cancel:取消指定的线程,默认取消类型为延时,直到该线程调用 pthread_testcancel 函数为止。类型修改使用 pthread_setcanceltype 函数。原型:

int pthread_cancel(pthread_t thread);

        参数:

        thread:要取消的线程的ID。

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

2、用例

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>void *task(void * argv){printf("thread strat\n");//默认取消类型为延时,到调用 pthread_testcancel 函数为止printf("working\n");sleep(1);pthread_testcancel();printf("arter cancel\n");return NULL;}
int main(int argc, char const *argv[])
{pthread_t pid;pthread_create(&pid,NULL,task,NULL);//取消子线程if(pthread_cancel(pid) != 0){perror("pthread_cancel");}void *res;//等待子线程终止并获取退出状态pthread_join(pid,&res);if (res == PTHREAD_CANCELED){printf("cancel succeed\n");}else{printf("cancel defent\n");}return 0;
}

三、线程同步

        只要使用线程就一定会存在竞态。那么什么是竞态呢?

        竞态是指当多个线程同时访问和操作共享的数据时,最终的结果依赖于线程执行的具体顺序,而这个顺序是无法确定的,可能会发生错误。比如创建10000个线程对一个全局变量进行累加。由于多个线程并发执行,就可能导致多个线程读到同一个值然后执行 +1 后写回变量中。从而导致最终的累加结果小于10000。

        竞态的解决办法就是锁。通过锁住一个线程,让该线程执行的时候不被其他线程打扰,从而解决竞态。

1、互斥锁(Mutex)

①:相关函数

        互斥锁是一种同步原语,用于保护共享资源,确保在同一时间只有一个线程可以访问该资源。

        相关函数主要包括以下几个:

        pthread_mutex_init 函数用于显示初始化互斥锁变量。它的原型如下:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

        其中,mutex是一个指向互斥锁变量的指针,attr参数可以用来设置互斥锁的属性,可以传入NULL使用默认属性。函数成功返回0,失败返回一个错误码。

        下面代码用于静态初始化互斥锁,静态初始化可以不调用pthread_mutex_destroy函数销毁互斥锁。如果是动态分配或者被跨多个函数或文件使用则需要显式销毁。

static pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;

        pthread_mutex_destroy 函数用于销毁互斥锁变量,释放相关资源。它的原型如下:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

        其中,mutex是一个指向互斥锁变量的指针。函数成功返回0,失败返回一个错误码。

        pthread_mutex_lock 函数用于加锁,即获取互斥锁。如果互斥锁已经被其他线程锁定,当前线程将阻塞,直到互斥锁被释放。它的原型如下:

int pthread_mutex_lock(pthread_mutex_t *mutex);

        其中,mutex是一个指向互斥锁变量的指针。函数成功返回0,失败返回一个错误码。

        pthread_mutex_unlock 函数用于解锁,即释放互斥锁。它的原型如下:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

        其中,mutex是一个指向互斥锁变量的指针。函数成功返回0,失败返回一个错误码。

        互斥锁只能在同一进程的不同线程之间同步,不能用于进程间的同步。在多线程程序中使用互斥锁时,需要保证所有线程使用的是同一个互斥锁变量。

        使用互斥锁的基本流程是先初始化互斥锁,然后在需要保护共享资源的代码段前后加锁和解锁操作。

②:用例

        该代码实现的是在两万个线程并发执行对同一变量进行累加时使用互斥锁进行保护。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>#define THREAD_COUNT 20000//静态初始化锁    如果是动态分配或者被跨多个函数或文件使用则需要显式销毁
static pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;//创建多个线程
void * add_thread(void *argv){int *p = (int *)argv;//累加之前获取锁,保证同一时间只有一个线程使用pthread_mutex_lock(&counter_mutex);//pthread_mutex_trylock(不会阻塞,而是返回EBUSY)(*p)++;//释放锁pthread_mutex_unlock(&counter_mutex);return (void *)0;
}int main(int argc, char const *argv[])
{pthread_t pid[THREAD_COUNT];int sum = 0;for (size_t i = 0; i < THREAD_COUNT; i++){//创建的线程的功能是给传入的参数累加1pthread_create(pid+i,NULL,add_thread,&sum);}//等待所有线程结束for (size_t i = 0; i < THREAD_COUNT; i++){pthread_join(pid[i],NULL);}printf("最终结果为 %d\n",sum);return 0;
}

2、读写锁(Read-Write Lock)

①:相关函数

        读写锁允许多个线程同时读取共享数据,但只允许一个线程写入共享数据。

        相关函数主要包括以下几个:

    pthread_rwlock_t类型:读写锁的类型,在使用前需要进行初始化。

    pthread_rwlock_init函数:用于初始化读写锁。它的原型如下:

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

        参数rwlock是指向要初始化的读写锁的指针,attr是读写锁的属性,可以传递NULL使用默认属性。函数调用成功时返回0,失败时返回错误码。

        下面代码用于静态初始化:

static pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

    pthread_rwlock_destroy函数:用于销毁读写锁。它的原型如下:

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

        参数rwlock是指向要销毁的读写锁的指针。函数调用成功时返回0,失败时返回错误码。

    pthread_rwlock_rdlock函数:用于获取读锁。它的原型如下:

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

        参数rwlock是指向要获取读锁的读写锁的指针。函数调用成功时返回0,失败时返回错误码。

    pthread_rwlock_wrlock函数:用于获取写锁。它的原型如下:

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

        参数rwlock是指向要获取写锁的读写锁的指针。函数调用成功时返回0,失败时返回错误码。

    pthread_rwlock_unlock函数:用于释放读锁或写锁。它的原型如下:

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

        参数rwlock是指向要释放读锁或写锁的读写锁的指针。函数调用成功时返回0,失败时返回错误码。

        读写锁的获取和释放应该配对使用,即获取读锁后应该释放读锁,获取写锁后应该释放写锁,否则可能会导致死锁或其它并发问题。

②:用例

        通过两个写线程和六个读线程来验证读写锁的作用。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>//static pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;静态初始化
pthread_rwlock_t rwlock;
int shared_data = 0;void *lock_reader(void *argv){pthread_rwlock_rdlock(&rwlock);printf("this is %s,value is %d\n",(char *)argv,shared_data);pthread_rwlock_unlock(&rwlock);
}void *lock_writer(void *argv){//给线程添加写锁pthread_rwlock_wrlock(&rwlock);int tep = shared_data + 1;sleep(1);shared_data = tep;printf("this is %s,shared_data++\n",(char *)argv);pthread_rwlock_unlock(&rwlock);
}int main(int argc, char const *argv[])
{// 避免写饥饿,将写优先级设置高于读优先级// pthread_rwlockattr_t attr;// pthread_rwlockattr_init(&attr);// pthread_rwlockattr_setkind_np(&attr,PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);// pthread_rwlock_init(&rwlock,&attr);//显式初始化pthread_rwlock_init(&rwlock,NULL);pthread_t writer1,writer2,reader1,reader2,reader3,reader4,reader5,reader6;//创建两个写线程pthread_create(&writer1,NULL,lock_writer,"writer1");pthread_create(&writer2,NULL,lock_writer,"writer2");sleep(3);pthread_create(&reader1,NULL,lock_reader,"reader1");pthread_create(&reader2,NULL,lock_reader,"reader2");pthread_create(&reader3,NULL,lock_reader,"reader3");pthread_create(&reader4,NULL,lock_reader,"reader4");pthread_create(&reader5,NULL,lock_reader,"reader5");pthread_create(&reader6,NULL,lock_reader,"reader6");//等待创建的子线程运行完成pthread_join(writer1,NULL);pthread_join(writer2,NULL);pthread_join(reader1,NULL);pthread_join(reader2,NULL);pthread_join(reader3,NULL);pthread_join(reader4,NULL);pthread_join(reader5,NULL);pthread_join(reader6,NULL);//销毁读写锁pthread_rwlock_destroy(&rwlock);return 0;
}

3、自旋锁

        自旋锁是一种基于忙等待的同步原语,它使得一个线程在获取锁失败时不会立即阻塞,而是反复地检查锁是否可用,直到获取到锁为止。

        自旋锁适用于保护临界区比较短的情况。如果临界区的执行时间比较长,自旋等待会造成CPU资源的浪费。在这种情况下,应该考虑使用其他同步原语,比如互斥锁或条件变量。

4、条件变量(condition variable)

①:相关函数

        条件变量是一种用于线程间通信的机制,它可以使一个线程等待,直到另一个线程满足某个条件。

        条件变量需要和互斥锁(mutex)一起使用,因为条件变量的等待和唤醒操作需要在互斥锁的保护下进行,以确保线程间的同步和互斥。

        相关函数有以下几个:

    pthread_cond_init:用于初始化一个条件变量。函数原型如下:

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

        参数cond是一个指向条件变量对象的指针,attr是一个指向条件变量属性的指针(通常可以传入NULL)。

        下面代码用于静态初始化:

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

    pthread_cond_destroy:用于销毁一个条件变量。函数原型如下:

int pthread_cond_destroy(pthread_cond_t *cond);

        参数cond是一个指向要销毁的条件变量对象的指针。

    pthread_cond_wait:用于使线程等待条件变量满足。函数原型如下:

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

        参数cond是一个指向要等待的条件变量对象的指针,mutex是一个指向互斥锁对象的指针。调用该函数后,线程会阻塞,直到另一个线程调用pthread_cond_signal()pthread_cond_broadcast()来唤醒等待的线程。

    pthread_cond_signal:用于唤醒一个等待的线程(如果在这个函数调用之前没有调用pthread_cond_wait 函数,则该函数不会唤醒任何线程)。函数原型如下:

int pthread_cond_signal(pthread_cond_t *cond);

        参数cond是一个指向要唤醒的条件变量对象的指针。调用该函数会唤醒一个处于等待状态的线程。

    pthread_cond_broadcast:用于广播唤醒所有等待的线程。函数原型如下:

int pthread_cond_broadcast(pthread_cond_t *cond);

        参数cond是一个指向要广播唤醒的条件变量对象的指针。调用该函数会唤醒所有处于等待状态的线程。

        使用条件变量的一般流程如下:

        1、初始化条件变量和互斥锁。

        2、在需要等待条件变量满足的线程中,使用pthread_cond_wait()函数等待条件变量。

        3、在满足条件时,通过持有互斥锁来保护共享数据,然后对需要等待的线程进行唤醒:如果只需唤醒一个线程,可以使用pthread_cond_signal()函数。如果需要唤醒所有等待线程,可以使用pthread_cond_broadcast()函数。

        4、销毁条件变量和互斥锁。

②:用例

        该用例创建两个线程,然后在两个线程中分别进行读写操作:写满数据后唤醒读操作,读完数据后唤醒写操作。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int count = 0;//初始化互斥锁
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//初始化条件变量
static pthread_mutex_t cond = PTHREAD_COND_INITIALIZER;//读数据
void *consumer(void *argv){pthread_mutex_lock(&mutex);while (1){//获取锁// pthread_mutex_lock(&mutex);if (count == 0){//缓存中没数据,,暂停线程pthread_cond_wait(&cond,&mutex);}printf("接收到的数字为%d\n",buffer[--count]);//唤醒生产者pthread_cond_signal(&cond);//释放锁// pthread_mutex_unlock(&mutex);}pthread_mutex_unlock(&mutex);}
//写数据
void *producer(void *argv){int item = 1;pthread_mutex_lock(&mutex);while (1){//获取锁// pthread_mutex_lock(&mutex);//如果缓冲区满,使用条件变量暂停线程if (count == BUFFER_SIZE){//暂停线程pthread_cond_wait(&cond,&mutex);}//缓存区没满buffer[count++] = item++;printf("发送数字%d\n",buffer[count-1]);//唤醒消费者pthread_cond_signal(&cond);//释放锁// pthread_mutex_unlock(&mutex);}pthread_mutex_unlock(&mutex);
}int main(int argc, char const *argv[])
{//创建读写线程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;
}

5、信号量(Semaphore)

①:相关函数

        信号量可以用于线程间的通信,也可以同于进程间的通信,信号量可以分为有名和无名信号量,与管道类似。主要用来实现互斥和同步。

        互斥:确保多个进程或线程不会同时访问临界区。

        同步:协调多个进程或线程的执行顺序确保它们按照一定的顺序执行。

        相关函数有以下几个:

        sem_init:用于初始化一个信号量。

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

        其中,sem是一个信号量对象的指针,pshared指定信号量是在进程内共享(1)还是在线程内共享(0),value是信号量的初始值。

        sem_wait:用于获取一个信号量,如果信号量的值大于0,就将其减一;如果信号量的值等于0,调用线程将被阻塞,直到信号量的值大于0为止。

int sem_wait(sem_t *sem);

        sem_post:用于释放一个信号量,将信号量的值加一。 

int sem_post(sem_t *sem);

        sem_destroy:用于销毁一个信号量对象。 

int sem_destroy(sem_t *sem);

②:用例

        1、线程中使用匿名信号量控制两个线程有顺序的进行读写

#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>sem_t *full;
sem_t *empty;int shard_num;int rand_num(){srand(time(NULL));return rand();
}//实现发送读取发送读取依次按序执行
void *producer(void *argv){for (int i = 0; i < 5; i++){//获取信号量sem_wait(empty);printf("\n第%d轮数据传输\n",i + 1);sleep(1);shard_num = rand_num();//释放信号量sem_post(full);}
}void *consumer(void *argv){for (int i = 0; i < 5; i++){//获取信号量sem_wait(full);printf("\n第%d轮数据读取,数据为:%d\n",i + 1,shard_num);sleep(1);//释放信号量sem_post(empty);}  
}int main(int argc, char const *argv[])
{//初始化信号量full = malloc(sizeof(sem_t));empty = malloc(sizeof(sem_t));sem_init(empty,0,1);sem_init(full,0,0);//创建生产者,消费者线程pthread_t producer_id,consumer_id;pthread_create(&producer_id,NULL,producer,NULL);pthread_create(&producer_id,NULL,consumer,NULL);//等待线程全部执行完成pthread_join(producer_id,NULL);pthread_join(consumer_id,NULL);//摧毁信号量sem_destroy(empty);sem_destroy(full);return 0;
}

        2、进程中使用匿名信号量控制子进程先于父进程运行。(在进程中通讯信号量需创建在共享内存中)

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>int main(int argc, char const *argv[])
{char *shm_name = "unnamed_sem_shm";//创建内存共享对象int fd = shm_open(shm_name,O_CREAT|O_RDWR,0666);//调整大小ftruncate(fd,sizeof(sem_t));//映射到内存区sem_t *sem = mmap(NULL,sizeof(sem_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//初始化信号量sem_init(sem,1,0);pid_t pid = fork();if (pid < 0){perror("fork");}else if (pid == 0){sleep(1);//睡眠1s保证父进程先运行printf("this is son\n");sem_post(sem);//sem+1释放信号量,父进程被唤醒}else{sem_wait(sem);//因为sem=0,会阻塞printf("this is father\n");waitpid(pid,NULL,0);}//摧毁信号量// 父进程执行到此处子进程已执行完毕可以销毁信号量// 子进程执行到此处父进程仍在等待信号量此时销毁会导致未定义行为if (pid > 0){if(sem_destroy(sem) == -1){perror("sem_destroy");}}// 父子进程都应该解除映射关闭文件描述符if (munmap(sem, sizeof(sem_t)) == -1){perror("munmap");}if (close(fd) == -1){perror("close");}// shm_unlink 只能调用一次只在父进程中调用if (pid > 0){if (shm_unlink(shm_name) == -1){perror("father shm_unlink");}}return 0;
}

         3、在进程中使用有名信号量实现类似互斥锁的功能

        其中:shm_open;sem_close;sem_unlink 是有名信号量需要使用到的特殊函数。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <semaphore.h>int main(int argc, char const *argv[])
{char shm_value_name[100]={0};sprintf(shm_value_name,"/value%d",getpid());char *sem_name = "/named_sem_shm";// 1.创建共享内存对象int value_fd = shm_open(shm_value_name,O_RDWR | O_CREAT,0644);//初始化有名信号量sem_t *sem = sem_open(sem_name,O_CREAT,0666,1);if (value_fd < 0){perror("shm_open");exit(EXIT_FAILURE);}// 2.设置共享内存对象大小ftruncate(value_fd,sizeof(int));// 3.内存映射int *share = mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED,value_fd,0);if (share == MAP_FAILED){perror("mmap");exit(EXIT_FAILURE);}// 初始化共享变量的值*share = 0;// 4.使用内存映射实现进程间的通讯pid_t pid = fork();if (pid < 0){perror("fork");exit(EXIT_FAILURE);}else if (pid == 0){sem_wait(sem);int tmp = *share + 1;sleep(1);*share = tmp;sem_post(sem);}else{sem_wait(sem);int tmp = *share + 1;sleep(1);*share = tmp;sem_post(sem);// 等待子进程结束waitpid(pid,NULL,0);printf("the final share is %d\n",*share);       }// 5.释放映射区,父子都释放if (munmap(share,sizeof(int)) == -1){perror("munmap");}if(close(value_fd) == -1){perror("close_value");}if (sem_close(sem) == -1){perror("sem_close");}// 6.释放共享内存对象if (pid > 0){if (shm_unlink(shm_value_name) == -1){perror("value_unlink");}if (sem_unlink(sem_name) == -1){perror("value_unlink");}}return 0;
}

四、线程池

1、相关函数

        线程池是一种管理线程的机制,可以用于控制并发任务的数量。线程池可以提高程序的性能和吞吐量,同时减少线程的创建和销毁的开销。线程池通常由一个固定数量的线程组成,并使用任务队列来存储待执行的任务。

        要使用线程池,可以使用GLib库中的线程池相关函数。GLib是Gnome项目的核心库,提供了一组丰富的功能和工具。

        以下是使用GLib库中线程池相关函数的一般步骤:

        g_thread_pool_new 创建线程池:

GThreadPool *pool = g_thread_pool_new(worker_func, user_data, max_threads, exclusive);

    worker_func是线程池中线程的任务函数,用于执行具体的工作。

    user_data是传递给任务函数的用户数据。

    max_threads是线程池中允许的最大线程数。

    exclusive指定线程池是否为独占模式。

        g_thread_pool_push 添加任务到线程池:

g_thread_pool_push(pool, task_data, NULL);

    task_data是要执行的任务数据。

        g_thread_pool_wait 等待任务完成:

g_thread_pool_wait(pool);

        g_thread_pool_free 销毁线程池:

g_thread_pool_free(pool, TRUE, TRUE);

        第一个参数是线程池对象。

        第二个参数指定是否等待所有任务完成后再销毁线程池。

        第三个参数指定是否等待所有线程完成后再销毁线程池。

2、用例

        该用例创建10个任务并发运行,因为设置线程池允许最大线程数为5,所以运行结果应该是:先创建五个线程,然后等一个线程运行结束后再创建另一个线程,也就是说最多只有五个线程在同时运行。

#include <glib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>void task_func(gpointer data,gpointer user_data){int task_num = *(int *)data;free(data);printf("第 %d 个任务开始执行\n",task_num);sleep(1);printf("第 %d 个任务执行完成\n",task_num);
}int main(int argc, char const *argv[])
{//创建线程池GThreadPool *thread_pool = g_thread_pool_new(task_func,NULL,5,TRUE,NULL);//添加任务for (int i = 0; i < 10; i++){int *tmp = malloc(sizeof(int));*tmp = i+1;g_thread_pool_push(thread_pool,tmp,NULL);}//等待所有任务完成g_thread_pool_free(thread_pool,FALSE,TRUE);printf("All task completed\n");return 0;
}

        要运行含 glib.h 的函数需要先安装该库

sudo apt-get update
sudo apt-get install libglib2.0-dev

        然后在Makefile文件中链接该库

thread_pool_test: thread_pool_test.c-$(CC) -o $@ $^ `pkg-config --cflags --libs glib-2.0`-./$@-rm ./$@

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

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

相关文章

Elionix 电子束曝光系统

Elionix 电子束曝光系统 - 上海纳腾仪器有限公司 -

【FreeRTOS】信号量

1.概念 当访问一个共享资源时&#xff0c;两个任务&#xff0c;并发访问出现不一致的问题&#xff0c;需要通过信号量解决 那么信号量是如何解决这个问题的呢&#xff1f; 任务量你可以认为是一把锁&#xff0c;一个任务拿到这个锁之后访问这个临界资源&#xff0c; 其他任务…

如何设计出一个比较全面的测试用例

目录 1. 测试用例的基本要素(不需要执行结果) 2. 测试用例的给我们带来的好处 3. 用例编写步骤 4. 设计测试用例的方法 4.1 基于需求进行测试用例的设计 4.2 具体的设计方法 1.等价类 2.边界值 3.判定表&#xff08;因果图&#xff09; 4.正交表法 5.场景设计法 6.错误猜测…

视频去噪技术分享

视频去噪是一种视频处理技术&#xff0c;旨在从视频帧中移除噪声和干扰&#xff0c;提高视频质量。噪声可能由多种因素引起&#xff0c;包括低光照条件、高ISO设置、传感器缺陷等。视频去噪对于提升视频内容的可视性和可用性至关重要&#xff0c;特别是在安全监控、医疗成像和视…

【MySQL】基础部分——DDL,DML,DQL,DCL,函数,约束,多表查询,事务

个人学习记录&#xff0c;供以后回顾和复习 ubuntu下安装使用1.DDL&#xff0c;DML&#xff0c;DQL&#xff0c;DCLDDL数据库表 DML增改删 DQL条件查询分组查询排序查询分页查询 DCL管理用户权限控制 2.函数字符串函数数值函数日期函数流程函数 3.约束4.多表查询多表关系内连接…

【Git必看系列】—— Git巨好用的神器之git stash篇

应用场景 当我们开发一个新功能时会先从master拉出一个分支dev&#xff0c;然后在这个dev分支下吭哧吭哧的开始写代码开发新功能&#xff0c;就如下代码所示&#xff0c;我们在dev分支下开发Person类的新功能getId() public class Person {private int id;private String nam…

零工市场小程序:推动零工市场建设

人力资源和社会保障部在2024年4月发布了标题为《地方推进零工市场建设经验做法》的文章。 零工市场小程序的功能 信息登记与发布 精准匹配、推送 在线沟通 权益保障 零工市场小程序作为一个找零工的渠道&#xff0c;在往后随着技术的发展和政策的支持下&#xff0c;功能必然…

C++——初步认识C++和namespace的用法

1.编程语言排行榜 我们通过排行可以看出 C在变成语言中还是占据着重要的地位 2.C在工作领域中的应用 1.PC客户端开发。⼀般是开发Windows上的桌面软件&#xff0c;比如WPS之类的&#xff0c;技术栈的话⼀般是C和 QT&#xff0c;QT 是⼀个跨平台的 C图形用户界面&#xff08;G…

表格标记<table>

一.表格标记、 1table&#xff1a;表格标记 2.caption:表单标题标记 3.tr:表格行标记 4.td:表格中数据单元格标记 5.th:标题单元格 table标记是表格中最外层标记&#xff0c;tr表示表格中的行标记&#xff0c;一对<tr>表示表格中的一行&#xff0c;在<tr>中可…

Scrapy爬虫IP代理池:提升爬取效率与稳定性

在互联网时代&#xff0c;数据就是新的黄金。无论是企业还是个人&#xff0c;数据的获取和分析能力都显得尤为重要。而在众多数据获取手段中&#xff0c;使用爬虫技术无疑是一种高效且广泛应用的方法。然而&#xff0c;爬虫在实际操作中常常会遇到IP被封禁的问题。为了解决这个…

小程序构建npm失败

小程序构建npm失败 项目工程结构说明解决方法引入依赖导致的其他问题 今天在初始化后的小程序中引入TDesign组件库&#xff0c;构建npm时报错。 项目工程结构说明 初始化后的项目中&#xff0c;包含miniprogram文件夹和一些项目配置文件&#xff0c;在project.config.json文件中…

【TypeScript入坑】TypeScript 的复杂类型「Interface 接口、class类、Enum枚举、Generics泛型、类型断言」

TypeScript入坑 Interface 接口简介接口合并TS 强校验Interface 里支持方法的写入class 类应用接口接口之间互相继承接口定义函数interface 与 type 的异同小案例 class 类类的定义与继承类的访问类型构造器 constructor静态属性&#xff0c;Setter 和 Getter做个小案例抽象类 …

fiddler抓包06_抓取https请求(chrome)

课程大纲 首次安装Fiddler&#xff0c;抓https请求&#xff0c;除打开抓包功能&#xff08;F12&#xff09;还需要&#xff1a; ① Fiddler开启https抓包 ② Fiddler导出证书&#xff1b; ③ 浏览器导入证书。 否则&#xff0c;无法访问https网站&#xff08;如下图&#xff0…

(十五)、把自己的镜像推送到 DockerHub

文章目录 1、登录Docker Hub2、标记&#xff08;Tag&#xff09;镜像3、推送&#xff08;Push&#xff09;镜像4、查看镜像5、下载镜像6、设置镜像为公开或者私有 1、登录Docker Hub 需要科学上网 https://hub.docker.com/ 如果没有账户&#xff0c;需要先注册一个。登录命令如…

docker搭建个人网盘,支持多种格式,还能画图,一键部署

1&#xff09;效果 2&#xff09;步骤 2.1&#xff09;docker安装 docker脚本 bash <(curl -sSL https://cdn.jsdelivr.net/gh/SuperManito/LinuxMirrorsmain/DockerInstallation.sh)docker-compose脚本 curl -L "https://github.com/docker/compose/releases/late…

技术成神之路:设计模式(十四)享元模式

介绍 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构性设计模式&#xff0c;旨在通过共享对象来有效地支持大量细粒度的对象。 1.定义 享元模式通过将对象状态分为内部状态&#xff08;可以共享&#xff09;和外部状态&#xff08;不可共享&#xff09;&#xf…

基于SpringBoot+Vue的高校竞赛管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

力扣115-不同的子序列(Java详细题解)

题目链接&#xff1a;不同的子序列 前情提要&#xff1a; 因为本人最近都来刷dp类的题目所以该题就默认用dp方法来做。 dp五部曲。 1.确定dp数组和i下标的含义。 2.确定递推公式。 3.dp初始化。 4.确定dp的遍历顺序。 5.如果没有ac打印dp数组 利于debug。 每一个dp题目…

java使用ByteBuffer进行多文件合并和拆分

1.背景 因为验证证书的需要&#xff0c;需要把证书文件和公钥给到客户&#xff0c;考虑到多个文件交互的不便性&#xff0c;所以决定将2个文件合并成一个文件交互给客户。刚开始采用字符串拼接2个文件内容&#xff0c;但是由于是加密文件&#xff0c;采用字符串形式合并后&…

界面控件Telerik UI for WinForms 2024 Q3概览 - 支持合并单元格等

Telerik UI for WinForms拥有适用Windows Forms的110多个令人惊叹的UI控件。所有的UI for WinForms控件都具有完整的主题支持&#xff0c;可以轻松地帮助开发人员在桌面和平板电脑应用程序提供一致美观的下一代用户体验。 本文将介绍界面组件Telerik UI for WinForms在今年第一…