线程同步66666

 1. 概述

  • 当有多个线程访问同一个共享资源(临界资源)时,且不允许同时访问,那么就需要线程同步。
  • 常见的线程同步方式:互斥锁、读写锁、条件变量、信号量。

2. 互斥锁

互斥锁的方式可以简单概括为:锁定操作临界资源的代码片段,锁定后每次只能由一个线程来进行操作。这样能够解决多个线程同时访问临界资源造成的数据混乱,但是降低了执行效率(因为并行操作变成了串行操作)。

【1】互斥锁类型:pthread_mutex_t。创建一个该类型的变量就得到一把互斥锁。该变量中保存了锁的状态(打开还是锁定),若为锁定则保存了加锁的线程ID。锁定时其他线程将会阻塞,直到这个互斥锁被打开。

以下函数的返回值:成功返回0,失败返回错误号。

【2】初始化互斥锁:

//restrict:是一个修饰指针的关键字,该关键字修饰的指针可以访问指向的内存地址,其他指针不行
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                        const pthread_mutexattr_t *restrict attr);
//mutex: 互斥锁地址
//attr: 互斥锁属性,一般为NULL默认属性

【3】释放互斥锁资源:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

【4】加锁(阻塞):

int pthread_mutex_lock(pthread_mutex_t *mutex);
//若锁是打开的,那么加锁成功,锁会记录线程ID。
//若锁是锁定的,那么加锁失败,线程阻塞在此,直到上一个线程解锁。

【5】加锁(非阻塞):

int pthread_mutex_trylock(pthread_mutex_t *mutex);
//若加锁失败,则不会阻塞,而是直接返回错误号。

【6】解锁:

int pthread_mutex_unlock(pthread_mutex_t *mutex);
//解锁还需加锁人,哪个线程加的锁就得哪个线程来解锁。

程序实例:创建两个子线程对全局变量number进行+1操作。若不使用互斥锁,就会造成数据混乱。使用了互斥锁,运行正常。

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

pthread_mutex_t mutex;  //全局的互斥锁
int number = 0;         //全局变量

//线程t1执行函数
void* funA(void *arg)
{
    for (int i = 0; i < 50; i++) {
        pthread_mutex_lock(&mutex);
        int cur = number;
        cur++;
        usleep(10);
        number = cur;
        printf("线程1的ID: %ld, number: %d\n", pthread_self(), number);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

//线程t2执行函数
void* funB(void *arg)
{
    for (int i = 0; i < 50; i++) {
        pthread_mutex_lock(&mutex);
        int cur = number;
        cur++;
        number = cur;
        printf("线程2的ID: %ld, number: %d\n", pthread_self(), number);
        usleep(5);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

//主函数
int main(int argc, char **argv)
{
    /* 初始化互斥锁 */
    pthread_mutex_init(&mutex, NULL);

    /* 创建两个子线程 */
    pthread_t t1, t2;
    pthread_create(&t1, NULL, funA, NULL);
    pthread_create(&t2, NULL, funB, NULL);

    /* 阻塞回收两个子线程*/
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    /* 销毁互斥锁 */
    pthread_mutex_destroy(&mutex);
    return 0;
}

3. 死锁

  • 互斥锁若使用不当,就会造成死锁。一旦多线程造成死锁,就会使得所有线程处于阻塞状态,且无法解除。
  • 常见的死锁场景:①加锁后忘记解锁;②重复加锁;③存在多个共享资源,随意加锁。
  • 避免死锁:①多检查代码;②对共享资源访问完毕后解锁,或者在加锁时使用trylock非阻塞;③引入一些专门用于死锁检测的模块;④如果程序中有多把锁, 可以控制对锁的访问顺序。另外,在加其它锁之前先释放拥有的互斥锁。

4. 自旋锁

【1】介绍:

  • 自旋锁与互斥锁类似,但是自旋锁在试图上锁时是不断“自旋”查看锁是否被释放,CPU资源占用多。因此,使用自旋锁的效率不高,在用户程序中很少使用自旋锁,更多的是在内核中使用针对执行时间少的代码
  • 互斥锁是基于自旋锁实现的,所以自旋锁更加底层。

【2】自旋锁类型:pthread_spinlock_t。创建一个该类型的变量就得到一把自旋锁。

以下函数的返回值:成功返回0,失败返回错误号。

【3】初始化/销毁自旋锁:

int pthread_spin_init(pthread_spinlock_t *lock, int pshared);//初始化
//lock: 自旋锁的地址
//pshared: 表示自旋锁的进程共享属性
    //PTHREAD_PROCESS_SHARED: 共享自旋锁。该自旋锁可以在多个进程中的线程之间共享
    //PTHREAD_PROCESS_PRIVATE: 私有自旋锁。只有本进程内的线程才能够使用该自旋锁

int pthread_spin_destroy(pthread_spinlock_t *lock);//销毁

【4】自旋锁的加锁和解锁:

int pthread_spin_lock(pthread_spinlock_t *lock);     //加锁(一直自旋)
int pthread_spin_trylock(pthread_spinlock_t *lock);  //加锁(返回错误,错误码EBUSY)
int pthread_spin_unlock(pthread_spinlock_t *lock);   //解锁

程序实例:创建两个子线程来对全局变量+1(每个线程循环10000次)。

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

int count = 0;//全局变量(临界区资源)
pthread_spinlock_t locker;//自旋锁

/* 线程执行函数 */
void* doing(void* arg)
{
    for (int i = 0; i < 10000; ++i) {
        //自旋锁加锁
        pthread_spin_lock(&locker);
        //操作临界区资源
        int tmp = count;
        tmp++;
        count = tmp;
        //自旋锁解锁
        pthread_spin_unlock(&locker);
    }
    return NULL;
}

/* 主函数 */
int main(int argc, char** argv)
{
    //初始化自旋锁
    pthread_spin_init(&locker, PTHREAD_PROCESS_PRIVATE);

    //创建两个子线程
    pthread_t tid[2];
    for (int i = 0; i < 2; ++i) {
        if (pthread_create(&tid[i], NULL, doing, NULL)) {
            perror("pthread_create error");
            exit(-1);
        }
    }

    //阻塞回收子线程
    for (int i = 0; i < 2; ++i) {
        if (pthread_join(tid[i], NULL)) {
            perror("pthread_join error");
            exit(-1);
        }
    }

    //打印全局变量
    printf("count = %d\n", count);

    //销毁自旋锁
    pthread_spin_destroy(&locker);
    return 0; 
}

5. 读写锁

读写锁可以视为互斥锁的升级版,可以指定锁定的是读操作还是写操作,且同一时间内只能锁定其中一个操作。读写锁的使用方式与互斥锁相同。

【1】读写锁类型:pthread_rwlock_t。创建一个该类型的变量就得到一把读写锁。读写锁中保存了以下信息:①锁的状态(打开还是锁定);②锁的是哪个操作(读/写);③哪个线程锁定了这把锁。

【2】读写锁特点:

  • 使用读写锁的读锁锁定了临界区,那么读操作是并行的。
  • 使用读写锁的写锁锁定了临界区,那么写操作是串行的。
  • 使用读写锁的读锁和写锁分别锁定了两个临界区,那么访问写锁的线程优先进入。因为写锁的优先级高于读锁。

以下函数的返回值:成功返回0,失败返回错误号。

【3】初始化读写锁:

//restrict:是一个修饰指针的关键字,该关键字修饰的指针可以访问指向的内存地址,其他指针不行
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
           const pthread_rwlockattr_t *restrict attr);
//rwlock: 读写锁地址
//attr: 读写锁属性,一般为NULL默认属性

【3】释放读写锁资源:

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

【4】锁定读操作(阻塞):

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
//若读写锁均打开,那么锁定读操作成功。
//若读写锁中读锁锁定,那么锁定读操作成功。因为读锁是共享的。
//若读写锁中写锁锁定,那么会阻塞。

【5】锁定读操作(非阻塞):

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
//若读写锁均打开,那么锁定读操作成功。
//若读写锁中读锁锁定,那么锁定读操作成功。因为读锁是共享的。
//若读写锁中写锁锁定,那么会返回错误号,而不会阻塞。

【6】锁定写操作(阻塞):

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//若读写锁均打开,锁定写操作成功。
//若读写锁中读锁或写锁锁定了,那么就会阻塞。

【7】锁定写操作(非阻塞):

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
//若读写锁均打开,锁定写操作成功。
//若读写锁中读锁或写锁锁定了,那么返回错误号,而不会阻塞。

【8】解锁:

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
//不管锁定的是读锁还是写锁,都能解锁。

程序实例:创建3个子线程进行写操作,创建5个子线程进行读操作。它们都针对一个全局变量。


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

int number = 0;             //全局变量(临界资源)
pthread_rwlock_t rwlocker;  //全局的读写锁

/* 写线程执行函数 */
void* writeNum(void *arg)
{
    while (1) {
        pthread_rwlock_wrlock(&rwlocker);
        int cur = number;
        cur++;
        number = cur;
        printf("写操作完毕!number = %d, tid: %ld\n", number, pthread_self());
        pthread_rwlock_unlock(&rwlocker);
        usleep(rand() % 100);//让子线程交替写
    }
    return NULL;
}

/* 读线程执行函数 */
void* readNum(void *arg)
{
    while (1) {
        pthread_rwlock_rdlock(&rwlocker);
        printf("读操作完毕!number = %d, tid: %ld\n", number, pthread_self());
        pthread_rwlock_unlock(&rwlocker);
        usleep(rand() % 100);
    }
    return NULL;
}

/* 主函数 */
int main(int argc, char **argv)
{
    //初始化读写锁
    pthread_rwlock_init(&rwlocker, NULL);

    //创建8个线程,3个为写线程,5个为读线程
    pthread_t wtid[3];
    pthread_t rtid[5];
    for (int i = 0; i < 3; i++) {
        pthread_create(&wtid[i], NULL, writeNum, NULL);
    }
    for (int i = 0; i < 5; i++) {
        pthread_create(&rtid[i], NULL, readNum, NULL);
    }

    //主线程回收8个线程
    for (int i = 0; i < 3; i++) {
        pthread_join(wtid[i], NULL);
    }
    for (int i = 0; i < 5; i++) {
        pthread_join(rtid[i], NULL);
    }

    //释放读写锁
    pthread_rwlock_destroy(&rwlocker);
    return 0;
}

6. 条件变量

条件变量的作用是进行线程的阻塞,而不是线程同步。当满足某个特定条件时才会阻塞线程。一般用于生产者-消费者模型,且和互斥锁相互配合。

【1】条件变量类型:pthread_cond_t。被条件变量阻塞的线程的信息会被记录到该类型的变量中,以便在解除阻塞时使用。

【2】初始化条件变量:

int pthread_cond_init(pthread_cond_t *restrict cond,
      const pthread_condattr_t *restrict attr);
//cond:条件变量的地址
//attr:条件变量的属性,一般为NULL。

【3】释放条件变量资源:

int pthread_cond_destroy(pthread_cond_t *cond);

【4】阻塞线程:

// 线程阻塞函数, 哪个线程调用这个函数, 哪个线程就会被阻塞
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

//mutex是互斥锁,用于线程同步。
//当阻塞线程时,若线程已经对互斥锁mutex上锁,那么会将这把锁打开,这样做是为了避免死锁
//当线程解除阻塞时,函数内部会帮助这个线程再次将这个mutex锁上,继续向下访问临界区

【5】阻塞线程(时间到解除):

// 将线程阻塞一定的时间长度, 时间到达之后, 线程就解除阻塞了
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

// 以下结构体表示的时间是从1971.1.1到某个时间点的时间, 总长度使用秒/纳秒表示
struct timespec {
	time_t tv_sec;      /* Seconds */
	long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
};

//使用示例
time_t mytim = time(NULL);	// 1970.1.1 0:0:0 到当前的总秒数
struct timespec tmsp;
tmsp.tv_nsec = 0;
tmsp.tv_sec = time(NULL) + 100;	// 线程阻塞100s

【6】解除阻塞(至少一个线程):

int pthread_cond_signal(pthread_cond_t *cond);

【7】解除阻塞(全部线程):

int pthread_cond_broadcast(pthread_cond_t *cond);

生产者-消费者模型:

①若干个生产者线程:生产商品放入任务队列中,若任务队列满则阻塞,可以使用一个生产者条件变量来控制是否阻塞。

②若干个消费者线程:消费者从任务队列中拿走商品,若任务队列空则阻塞,可以使用一个消费者条件变量来控制是否阻塞。

③任务队列:可以是数组、链表、stl容器等等。

程序实例:使用条件变量实现生产者-消费者模型,生产者线程有5个,往链表头部添加节点;消费者线程也有5个,删除链表头部节点。

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

/* 单链表定义 */
typedef struct LNode {
    int number;
    struct LNode *next;
} LNode;

/* 全局变量及锁 */
pthread_mutex_t mutex;  //互斥锁
pthread_cond_t cond;    //条件变量
LNode *head = NULL;     //临界资源,单链表

/* 生产者执行函数 */
void* produce(void *arg)
{
    while (1) {
        //生产商品,往链表头部添加节点
        pthread_mutex_lock(&mutex);
        LNode *tmp = (LNode*)malloc(sizeof(LNode));//创建新节点
        tmp->number = rand() % 100;
        tmp->next = head;
        head = tmp;
        printf("生产完毕! 新节点number: %d, 线程ID: %ld\n", tmp->number, pthread_self());
        pthread_mutex_unlock(&mutex);
        //通知消费者拿走商品
        pthread_cond_broadcast(&cond);
        sleep(rand() % 3);//生产慢一点
    }
    return NULL;
}

/* 消费者执行函数 */
void* consume(void *arg)
{
    while (1) {
        pthread_mutex_lock(&mutex);
        //无商品则等待
        //这里不能用if,可能出现段错误,如下场景:
        //假设消费者线程1进入后阻塞,然后切换到生产者线程,解除其阻塞
        //然后切换到消费者线程2拿走了商品,此时链表又是空。
        //接着,切换回消费者线程1,由于if之前已经判定过了,这里直接进行后续操作,
        //从而出现段错误。因此通过while循环判断,当阻塞解除后也会再次判断。
        while (head == NULL) {
            pthread_cond_wait(&cond, &mutex);
        }
        //拿走商品,删除链表头部节点
        LNode *tmp = head;
        printf("消费完毕! 节点number: %d, 线程ID: %ld\n", tmp->number, pthread_self());
        head = head->next;
        free(tmp);
        pthread_mutex_unlock(&mutex);
        sleep(rand() % 3);
    }
    return NULL;
}

/* 主函数 */
int main(int argc, char **argv)
{
    //初始化锁和条件变量
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    //创建5个生产者线程和5个消费者线程
    pthread_t ptid[5];
    pthread_t ctid[5];
    for (int i = 0; i < 5; i++) {
        pthread_create(&ptid[i], NULL, produce, NULL);
    }
    for (int i = 0; i < 5; i++) {
        pthread_create(&ctid[i], NULL, consume, NULL);
    }

    //主线程回收10个线程
    for (int i = 0; i < 5; i++) {
        pthread_join(ptid[i], NULL);
    }
    for (int i = 0; i < 5; i++) {
        pthread_join(ctid[i], NULL);
    }

    //释放互斥锁和条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

7. 信号量

信号量(信号灯)用在多线程的多任务同步中,一个线程完成了某个任务就通过信号量告诉其它线程,其它线程再进行相关操作。信号量也是用于阻塞线程,要保证线程安全,需要信号量与互斥锁一起使用。

【1】信号量类型:sem_t。需要添加头文件<semaphore.h>.

【2】初始化信号量:

int sem_init(sem_t *sem, int pshared, unsigned int value);
//sem: 信号量地址
//pshared: 0表示线程同步,非0表示进程同步
//value: 初始化当前信号量拥有的资源数>=0, 若资源数为0则阻塞

【3】释放信号量资源:

int sem_destroy(sem_t *sem);
//sem: 信号量地址

【4】消耗资源函数(阻塞):

//sem: 信号量地址
//函数被调用,sem中的资源就会被消耗1个
//当资源数为0时,线程就会阻塞
int sem_wait(sem_t *sem);

【5】消耗资源函数(非阻塞):

//sem: 信号量地址
//函数被调用,sem中的资源就会被消耗1个
//当资源数为0时,线程就会返回错误号,而不会阻塞
int sem_trywait(sem_t *sem);

【6】时间到解除阻塞:

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
//sem: 信号量地址
//abs_timeout: 与pthread_cond_timedwait中的参数一样

【7】增加资源函数:

//给sem中的资源数+1
//若资源数从0加到1,那么阻塞的线程就会解除阻塞
int sem_post(sem_t *sem);

【8】查看资源数:

//查看信号量sem中的整形数值, 这个值会被写到sval指针对应的内存中
int sem_getvalue(sem_t *sem, int *sval);

程序实例:使用信号量实现生产者-消费者模型,生产者线程有5个,往链表头部添加节点;消费者线程也有5个,删除链表头部节点。同时,限定了平台容纳商品的最大容量为6个。我们可以设置两个信号量,分别来代表生产者线程生产的商品数量、平台中现有的商品数量(用于给消费者线程拿走)。

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

/* 单链表节点声明 */
typedef struct LNode{
    int number;
    struct LNode *next;
} LNode;

/* 全局变量 */
sem_t psem; //生产者生产商品的信号量
sem_t csem; //消费者拿走时的信号量
pthread_mutex_t mutex;  //互斥锁
LNode *head = NULL;     //单链表(临界区资源)

/* 生产者线程执行函数 */
void* produce(void *arg)
{
    while (1) {
        //生产者信号量-1
        sem_wait(&psem);
        //生产者生产商品
        pthread_mutex_lock(&mutex);
        LNode *tmp = (LNode*)malloc(sizeof(LNode));
        tmp->number = rand() % 100;
        tmp->next = head;
        head = tmp;
        printf("生产完毕!新节点number: %d, 线程ID: %ld\n", tmp->number, pthread_self());
        pthread_mutex_unlock(&mutex);
        //消费者信号量+1
        sem_post(&csem);
        sleep(rand() % 3);
    }
    return NULL;
}

/* 消费者线程执行函数 */
void* consume(void *arg)
{
    while (1) {
        //消费者信号量-1
        sem_wait(&csem);
        //消费者拿走商品
        pthread_mutex_lock(&mutex);
        LNode *tmp = head;
        head = head->next;
        printf("消费完毕!节点number: %d, 线程ID: %ld\n", tmp->number, pthread_self());
        free(tmp);
        pthread_mutex_unlock(&mutex);
        //生产者信号量+1
        sem_post(&psem);
        sleep(rand() % 3);
    }
    return NULL;
}

/* 主函数 */
int main(int argc, char **argv)
{
    //初始化信号量和互斥锁
    sem_init(&psem, 0, 6);//平台最多容纳的商品数
    sem_init(&csem, 0, 0);//平台最初没有商品
    pthread_mutex_init(&mutex, NULL);

    //创建5个生产者线程,5个消费者线程
    pthread_t ptid[5];
    pthread_t ctid[5];
    for (int i = 0; i < 5; i++) {
        pthread_create(&ptid[i], NULL, produce, NULL);
    }
    for (int i = 0; i < 5; i++) {
        pthread_create(&ctid[i], NULL, consume, NULL);
    }

    //释放线程资源
    for (int i = 0; i < 5; i++) {
        pthread_join(ptid[i], NULL);
    }
    for (int i = 0; i < 5; i++) {
        pthread_join(ctid[i], NULL);
    }

    //释放其它资源
    sem_destroy(&psem);
    sem_destroy(&csem);
    pthread_mutex_destroy(&mutex);
    return 0;
}

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

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

相关文章

ros1仿真导航机器人 navigation

仅为学习记录和一些自己的思考&#xff0c;不具有参考意义。 1navigation导航框架 2导航设置过程 &#xff08;1&#xff09;启动仿真环境 roslaunch why_simulation why_robocup.launch &#xff08;2&#xff09;启动move_base导航、amcl定位 roslaunch why_simulation nav…

【OnlyOffice】桌面应用编辑器,插件开发大赛,等你来挑战

OnlyOffice&#xff0c;桌面应用编辑器&#xff0c;最近版本已从8.0升级到了8.1 从PDF、Word、Excel、PPT等全面进行了升级。随着AI应用持续的火热&#xff0c;OnlyOffice也在不断推出AI相关插件。 因此&#xff0c;在此给大家推荐一下OnlyOffice本次的插件开发大赛。 详细信息…

磁盘就是一个超大的Byte数组,操作系统是如何管理的?

磁盘在操作系统的维度看&#xff0c;就是一个“超大的Byte数组”。 那么操作系统是如何对这块“超大的Byte数组”做管理的呢&#xff1f; 我们知道在逻辑上&#xff0c;上帝说是用“文件”的概念来进行管理的。于是&#xff0c;便有了“文件系统”。那么&#xff0c;文件系统…

深圳晶彩智能ESP32-2432S028R实时观察LVGL9效果

深圳晶彩智能ESP32-2432S028R概述&#xff1a; 深圳晶彩智能出品ESP32-32432S028R为2.8寸彩色屏采用分辨率320x240彩色液晶屏&#xff0c;驱动芯片是ILI9431。板载乐鑫公司出品ESP-WROOM-32&#xff0c;Flash 4M。型号尾部“R”标识电阻膜的感压式触摸屏&#xff0c;驱动芯片是…

以腾讯为例,手把手教你搭建产品帮助中心

一个精心设计的产品帮助中心对于提高用户满意度和体验至关重要。腾讯&#xff0c;作为全球领先的互联网企业&#xff0c;通过其多样化的产品线&#xff08;包括微信、QQ、腾讯游戏、腾讯视频等&#xff09;吸引了亿万用户。下面将以腾讯为例&#xff0c;向您展示如何搭建一个高…

数据库系统原理 | 查询作业1

整理自博主本科《数据库系统原理》专业课自己完成的实验课查询作业&#xff0c;以便各位学习数据库系统概论的小伙伴们参考、学习。 *文中若存在书写不合理的地方&#xff0c;欢迎各位斧正。 专业课本&#xff1a; ​ ———— 本次实验使用到的图形化工具&#xff1a;Heidisql…

进程的控制-ps和kill命令

ps 查看进程信息 部分参数&#xff1a; a : 显示现行终端机下的所有程序&#xff0c;包括其他用户的程序 u: 以用户为主的格式来显示程序状况 x: 显示所有程序&#xff0c;不以 终端机来区分 kill 向指定的进程发送信号 kill 可将指定的信息送至程序。预设的信息为 SIG…

linux下高级IO模型

高级IO 1.高级IO模型基本概念1.1 阻塞IO1.2 非阻塞IO1.3 信号驱动IO1.4 IO多路转接1.5 异步IO 2. 模型代码实现2.1 非阻塞IO2.2 多路转接-selectselect函数介绍什么才叫就绪呢&#xff1f;demoselect特点 2.3 多路转接-pollpoll函数介绍poll优缺点demo 2.4 多路转接-epoll&…

24西安电子科技大学马克思主义学院—考研录取情况

01、马克思主义学院各个方向 02、24马克思主义学院近三年复试分数线对比 PS&#xff1a;马院24年院线相对于23年院线增加15分&#xff0c;反映了大家对于马克思主义理论学习与研究的热情高涨&#xff0c;也彰显了学院在人才培养、学科建设及学术研究等方面的不断进步与成就。 6…

集成学习(一)Bagging

前边学习了&#xff1a;十大集成学习模型&#xff08;简单版&#xff09;-CSDN博客 Bagging又称为“装袋法”&#xff0c;它是所有集成学习方法当中最为著名、最为简单、也最为有效的操作之一。 在Bagging集成当中&#xff0c;我们并行建立多个弱评估器&#xff08;通常是决策…

限时免费!国产Sora快手可灵Web网页端及全新功能上线!国货之光!

大家好&#xff0c;我是程序员X小鹿&#xff0c;前互联网大厂程序员&#xff0c;自由职业2年&#xff0c;也一名 AIGC 爱好者&#xff0c;持续分享更多前沿的「AI 工具」和「AI副业玩法」&#xff0c;欢迎一起交流~ 快手可灵&#xff08;Kling&#xff09;这回是真的出息了&…

DP:二维费用背包问题

文章目录 &#x1f3b5;二维费用背包问题&#x1f3b6;引言&#x1f3b6;问题定义&#x1f3b6;动态规划思想&#x1f3b6;状态定义和状态转移方程&#x1f3b6;初始条件和边界情况 &#x1f3b5;例题&#x1f3b6;1.一和零&#x1f3b6;2.盈利计划 &#x1f3b5;总结 &#x1…

身体(body)的觉醒:如果你贪婪,给你整个宇宙都不够

佛&#xff0c;是一个梵文的汉语音译词&#xff0c;指觉醒者。 何谓觉醒&#xff1f;什么的觉醒&#xff1f;其实很简单&#xff0c;就是身体的觉醒。 佛的另一个名字&#xff0c;叫菩提&#xff0c;佛就是菩提&#xff0c;菩提老祖&#xff0c;就是佛祖。 一、body&#xff…

跟《经济学人》学英文:2024年07月06日这期:Finishing schools for the age of TikTok

Finishing schools for the age of TikTok Unsure how to be polite at work? Ask a digital etiquette guru 不确定如何在工作中保持礼貌&#xff1f;请教一位数字礼仪大师 “Finishing schools” 是指专门为年轻女性提供礼仪、社交技巧、文化修养等教育的学校&#xff0c;…

WordPress网站添加插件和主题时潜在危险分析

WordPress 最初只是一个简单的博客软件&#xff0c;现在据估计为全球前 1000 万个网站中的 30% 提供支持。WordPress受欢迎的因素之一是可以轻松创建插件和主题来扩展它并提供比默认设置更多的功能。 目前&#xff0c;WordPress 网站列出了 56,000 多个插件以及数千个主题。插件…

【刷题汇总--大数加法、 链表相加(二)、大数乘法】

C日常刷题积累 今日刷题汇总 - day0061、大数加法1.1、题目1.2、思路1.3、程序实现 2、 链表相加(二)2.1、题目2.2、思路2.3、程序实现 3、大数乘法3.1、题目3.2、思路3.3、程序实现 4、题目链接 今日刷题汇总 - day006 1、大数加法 1.1、题目 1.2、思路 读完题,明白大数相加…

【C++干货基地】C++模板深度解析:进阶技巧与高级特性掌握(按需实例化、全特化与偏特化)文末送书

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

带你解刨自动化测试框架详细总结

自动化测试框架概念 自动化测试框架是一个集成体系&#xff0c;这个体系中包含测试功能的函数库、测试数据源、测试对象以及可重用的模块。 框架&#xff08;framework&#xff09;是一个框子——指其约束性&#xff0c;也是一个架子——指其支撑性。是一个基本概念上的结构&…

go zero入门

一、goctl安装 goctl 是 go-zero 的内置脚手架&#xff0c;可以一键生成代码、文档、部署 k8s yaml、dockerfile 等。 # Go 1.16 及以后版本 go install github.com/zeromicro/go-zero/tools/goctllatest检查是否安装成功 $ goctl -v goctl version 1.6.6 darwin/amd64vscod…

String类对象比较:==和equals的具体细节

public class test {public static void main(String[] args) {String name1 "zzz";String name2 "zzz";String name3 new String("zzz");// hashCode() 方法&#xff1a;基于字符串的内容计算哈希值&#xff0c;因此内容相同的字符串对象其 …