Linux系统编程(七):线程同步

参考引用

  • UNIX 环境高级编程 (第3版)
  • 黑马程序员-Linux 系统编程

1. 同步概念

  • 所谓同步,即同时起步、协调一致。不同的对象,对 “同步” 的理解方式略有不同
    • 设备同步,是指在两个设备之间规定一个共同的时间参考
    • 数据库同步,是指让两个或多个数据库内容保持一致,或者按需要部分保持一致
    • 文件同步,是指让两个或多个文件夹里的文件保持一致
  • 编程中、通信中所说的同步:“同” 字应是指协同、协助、互相配合。主旨在协同步调,按预定的先后次序运行

1.1 线程同步

  • 线程同步,是指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能

在这里插入图片描述

  • 产生的现象叫做 “与时间有关的错误” (time related)。为了避免这种数据混乱,线程需要同步
    • “同步” 的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间、信号间等等都需要同步机制。因此,所有 “多个控制流,共同操作一个共享资源” 的情况,都需要同步

1.2 数据混乱原因

  • 1、资源共享 (独享资源则不会)
  • 2、调度随机 (意味着数据访问会出现竞争)
  • 3、线程间缺乏必要的同步机制

以上 3 点中,前两点不能改变,欲提高效率,传递数据时资源必须共享。只要共享资源,就一定会出现竞争只要存在竞争关系,数据就很容易出现混乱。所以只能从第 3 点着手解决,使多个线程在访问共享资源的时候出现互斥

2. 互斥量 mutex

  • Linux 中提供一把互斥锁 mutex (也称之为互斥量)。每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。资源还是共享的,线程间也还是竞争的,但通过 “锁” 就将资源的访问变成互斥操作

在这里插入图片描述

  • 但应注意:同一时刻,只能有一个线程持有该锁。当 A 线程对某个全局变量加锁访问,B 在访问前尝试加锁,拿不到锁则 B 阻塞。C 线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱
    • 所以,互斥锁实质上是操作系统提供的一把 “建议锁” (又称 “协同锁”),建议程序中有多线程访问共享资源的时候使用该机制,并没有强制限定
    • 因此,即使有了 mutex,如果有线程不按规则来访问数据,依然会造成数据混乱

2.1 主要应用函数

#include <pthread.h>// 返回值都是: 成功返回 0,失败返回错误号
// pthread_mutex_t 类型,其本质是一个结构体
// pthread_mutex_t mutex; 变量 mutex 只有两种取值 1、0// 初始化一个互斥锁(互斥量) --> 初值可看作 1
// 参 1: 传出参数,调用时应传 &mutex
// restrict 用来限定指针变量。被该关键字限定的指针变量所指向的内存操作,必须由本指针完成
// 参 2: 互斥量属性。是一个传入参数,通常传 NULL,选用默认属性(线程间共享)
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);// 销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);// 加锁。可理解为将 mutex-- (或 -1),操作后 mutex 的值为 0
int pthread_mutex_lock(pthread_mutex_t *mutex);// 解锁。可理解为将 mutex++ (或 +1),操作后 mutex 的值为 1
int pthread_mutex_unlock(pthread_mutex_t *mutex);// 尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);

2.2 加锁与解锁

2.2.1 lock 和 unlock
  • lock 尝试加锁,如果加锁不成功则线程阻塞,阻塞到持有该互斥量的其他线程解锁为止
  • unlock 主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认: 先阻塞、先唤醒
  • 例如:T1 T2 T3 T4 使用一把 mutex 锁。T1 加锁成功,其他线程均阻塞,直至 T1 解锁。T1 解锁后,T2 T3 T4 均被唤醒,并自动再次尝试加锁。可假想 mutex 锁 init 成功初始值为 1,lock 功能是将 mutex–,而 unlock 则将 mutex++
2.2.2 lock 和 trylock
  • lock 加锁失败会阻塞,等待锁释放
  • trylock 加锁失败直接返回错误号 (如:EBUSY),不阻塞
  • 在访问共享资源前加锁,访问结束后立即解锁。锁的 “粒度” 应越小越好

2.3 加锁步骤测试

  • 创建锁 --> 初始化 --> 加锁 --> 访问共享数据 --> 解锁 --> 销毁锁
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <pthread.h>pthread_mutex_t mutex;   // 定义一把全局互斥锁void *tfn(void *arg) {srand(time(NULL));while(1) {pthread_mutex_lock(&mutex);    // 加锁,为 0printf("hello ");sleep(rand() % 3);             // 模拟长时间操作共享资源,导致 cpu 易主,产生与时间有关的错误printf("world\n");pthread_mutex_unlock(&mutex);  // 解锁,为 1sleep(rand() % 3); }   return NULL;
    }int main(int argc, char *argv[]) {pthread_t tid;srand(time(NULL));int ret = pthread_mutex_init(&mutex, NULL);  // 初始化互斥锁,为 1if (ret != 0) {fprintf(stderr, "mutex init error: %s\n", strerror(ret));exit(1);}   pthread_create(&tid, NULL, tfn, NULL);while (1) {pthread_mutex_lock(&mutex);      // 加锁printf("HELLO ");sleep(rand() % 3); printf("WORLD\n");pthread_mutex_unlock(&mutex);    // 解锁sleep(rand() % 3); }   pthread_join(tid, NULL);pthread_mutex_destroy(&mutex);       // 销毁互斥锁return 0;
    }
    
    $ gcc pthread_shared.c -o pthread_shared -pthread
    $ ./pthread_shared
    HELLO WORLD
    hello world
    HELLO WORLD
    ...
    

3. 死锁

  • 是使用锁不恰当导致的现象
    • 线程试图对同一个互斥量 A 加锁两次(对一个锁反复 lock
    • 线程 1 拥有 A 锁,请求获得 B 锁;线程 2 拥有 B 锁,请求获得 A 锁(两个线程各自持有一把锁,请求另一把

案例

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>#if 1
int var = 1, num = 5;
pthread_mutex_t m_var, m_num;void *tfn(void *arg) {int i = (int)arg;if (i == 1) {pthread_mutex_lock(&m_var);var = 22;sleep(1);       //给另外一个线程加锁,创造机会.pthread_mutex_lock(&m_num);num = 66; pthread_mutex_unlock(&m_var);pthread_mutex_unlock(&m_num);printf("----thread %d finish\n", i);pthread_exit(NULL);} else if (i == 2) {pthread_mutex_lock(&m_num);var = 33;sleep(1);pthread_mutex_lock(&m_var);num = 99; pthread_mutex_unlock(&m_var);pthread_mutex_unlock(&m_num);printf("----thread %d finish\n", i);pthread_exit(NULL);}return NULL;
}int main(void) {pthread_t tid1, tid2;int ret1, ret2;pthread_mutex_init(&m_var, NULL);pthread_mutex_init(&m_num, NULL);pthread_create(&tid1, NULL, tfn, (void *)1);pthread_create(&tid2, NULL, tfn, (void *)2);sleep(3);printf("var = %d, num = %d\n", var, num);ret1 = pthread_mutex_destroy(&m_var);      //释放琐ret2 = pthread_mutex_destroy(&m_num);if (ret1 == 0 && ret2 == 0) printf("------------destroy mutex finish\n");pthread_join(tid1, NULL);pthread_join(tid2, NULL);printf("------------join thread finish\n");return 0;
}
#else int a = 100;int main(void) {pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock(&mutex);a = 777;pthread_mutex_lock(&mutex);pthread_mutex_unlock(&mutex);printf("-----------a = %d\n", a);pthread_mutex_destroy(&mutex);return 0;
}#endif
$ gcc deadlock.c -o deadlock -pthread
$ ./deadlock 
var = 33, num = 5
...

4. 读写锁

  • 与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享,写锁优先级高

4.1 读写锁状态

  • 读写锁只有一把,但其具备两种状态
    • 读模式下加锁状态 (读锁)
    • 写模式下加锁状态 (写锁)

4.2 读写锁特性

  • 读写锁是 “写模式加锁” 时, 解锁前,所有对该锁加锁的线程都会被阻塞
  • 读写锁是 “读模式加锁” 时, 如果线程以读模式对其加锁会成功,如果线程以写模式加锁会阻塞
  • 读写锁是 “读模式加锁” 时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程
    • 读写锁会阻塞随后的读模式锁请求
    • 优先满足写模式锁
    • 读锁、写锁并行阻塞,写锁优先级高

读写锁非常适合于对数据结构读的次数远大于写的情况

4.3 主要应用函数

#include <pthread.h>// 返回值 成功返回 0,失败直接返回错误号// 初始化一把读写锁
// 参 2: attr 表读写锁属性,通常使用默认属性,传 NULL 即可
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);// 销毁一把读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);// 以读方式请求读写锁 (简称:请求读锁)
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);// 解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

4.4 读写锁示例

  • 3 个线程不定时 “写” 全局资源,5 个线程不定时 “读” 同一全局资源
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>int counter;                          // 全局资源
pthread_rwlock_t rwlock;void *th_write(void *arg) {int t;int i = (int)arg;while (1) {t = counter;                  // 保存写之前的值usleep(1000);pthread_rwlock_wrlock(&rwlock);printf("=======write %d: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);pthread_rwlock_unlock(&rwlock);usleep(9000);                 // 给 r 锁提供机会}return NULL;
}void *th_read(void *arg) {int i = (int)arg;while (1) {pthread_rwlock_rdlock(&rwlock);printf("----------------------------read %d: %lu: %d\n", i, pthread_self(), counter);pthread_rwlock_unlock(&rwlock);usleep(2000);      // 给写锁提供机会}return NULL;
}int main(void) {int i;pthread_t tid[8];pthread_rwlock_init(&rwlock, NULL);for (i = 0; i < 3; i++)pthread_create(&tid[i], NULL, th_write, (void *)i);for (i = 0; i < 5; i++)pthread_create(&tid[i+3], NULL, th_read, (void *)i);for (i = 0; i < 8; i++)pthread_join(tid[i], NULL);pthread_rwlock_destroy(&rwlock);   // 释放读写琐return 0;
}
$ gcc rwlock.c -o rwlock -pthread
$ ./rwlock 
----------------------------read 0: 140472231028480: 0
----------------------------read 3: 140472205850368: 0
----------------------------read 2: 140472214243072: 0
----------------------------read 4: 140472197457664: 0
----------------------------read 1: 140472222635776: 0
=======write 0: 140472256206592: counter=0 ++counter=1
=======write 1: 140472247813888: counter=0 ++counter=2
=======write 2: 140472239421184: counter=0 ++counter=3
----------------------------read 2: 140472214243072: 3
----------------------------read 3: 140472205850368: 3
...

5. 条件变量

  • 条件变量本身不是锁,但它也可以造成线程阻塞
  • 通常与互斥锁配合使用,给多线程提供一个会合的场所

5.1 主要应用函数

#include <pthread.h>// 返回值 成功返回 0,失败直接返回错误号// 初始化一个条件变量
// 参 2: attr 表条件变量属性,通常为默认值,传 NULL 即可
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);// 销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);// 函数作用(1、2 两步为一个原子操作)// 1、阻塞等待条件变量 cond (参 1) 满足// 2、释放已掌握的互斥锁 (解锁互斥量),相当于 pthread_mutex_unlock(&mutex);// 3、当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex_lock(&mutex);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);// 限时等待一个条件变量
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);// 唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_signal(pthread_cond_t *cond);// 唤醒全部阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);

在这里插入图片描述

5.2 生产者消费者条件变量模型

  • 线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法
    • 假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源 (一般称之为汇聚),生产者向其中添加产品,消费者从中消费掉产品

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>void err_thread(int ret, char *str) {if (ret != 0) {fprintf(stderr, "%s:%s\n", str, strerror(ret));pthread_exit(NULL);}
}struct msg {int num;struct msg *next;
};struct msg *head;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;       // 定义/初始化一个互斥量
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER;      // 定义/初始化一个条件变量void *produser(void *arg) {while (1) {struct msg *mp = malloc(sizeof(struct msg));mp->num = rand() % 1000 + 1;                        // 模拟生产一个数据`printf("--produce %d\n", mp->num);pthread_mutex_lock(&mutex);                         // 加锁 互斥量mp->next = head;                                    // 写公共区域head = mp;pthread_mutex_unlock(&mutex);                       // 解锁 互斥量pthread_cond_signal(&has_data);                     // 唤醒阻塞在条件变量 has_data上的线程.sleep(rand() % 3);}return NULL;
}void *consumer(void *arg) {while (1) {struct msg *mp;pthread_mutex_lock(&mutex);                         // 加锁 互斥量while (head == NULL) {pthread_cond_wait(&has_data, &mutex);           // 阻塞等待条件变量, 解锁}                                                   // pthread_cond_wait 返回时, 重新加锁 mutexmp = head;head = mp->next;pthread_mutex_unlock(&mutex);                       // 解锁 互斥量printf("---------consumer id: %lu :%d\n", pthread_self(), mp->num);free(mp);sleep(rand()%3);}return NULL;
}int main(int argc, char *argv[]) {int ret;pthread_t pid, cid;srand(time(NULL));ret = pthread_create(&pid, NULL, produser, NULL);           // 生产者if (ret != 0) err_thread(ret, "pthread_create produser error");ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者if (ret != 0) err_thread(ret, "pthread_create consuer error");ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者if (ret != 0) err_thread(ret, "pthread_create consuer error");ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者if (ret != 0) err_thread(ret, "pthread_create consuer error");pthread_join(pid, NULL);pthread_join(cid, NULL);return 0;
}
$ gcc cond.c -o cond -pthread
$ ./cond 
--produce 208
---------consumer id: 140611653785344 :208
--produce 829
---------consumer id: 140611645392640 :829
--produce 191
--produce 625
---------consumer id: 140611662178048 :625
---------consumer id: 140611653785344 :191
--produce 926
---------consumer id: 140611645392640 :926
...

5.3 条件变量的优点

  • 相较于 mutex 而言,条件变量可以减少竞争
    • 若直接使用 mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚 (链表) 中没有数据,消费者之间竞争互斥锁是无意义的
    • 有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争,提高了程序效率

6. 信号量

  • 信号量相当于初始化值为 N 的互斥量

    • 由于互斥锁的粒度比较大,如果希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导致线程的并发性下降。线程从并行执行,变成了串行执行,与直接使用单进程无异
  • 信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发

6.1 主要应用函数

#include <semaphore.h>// 返回值 成功返回 0,失败返回-1,同时设置 errno
// 规定信号量 sem 不能 < 0// 初始化一个信号量// 参 1: sem 信号量// 参 2: pshared 取 0 用于线程间; 取非 (一般为 1) 用于进程间// 参 3: value 指定信号量初值
int sem_init(sem_t *sem, int pshared, unsigned int value);// 销毁一个信号量
int sem_destroy(sem_t *sem);// 给信号量加锁 --
int sem_wait(sem_t *sem);
// 尝试对信号量加锁 -- (与 sem_wait 的区别类比 lock 和 trylock)
int sem_trywait(sem_t *sem);
// 限时尝试对信号量加锁 --
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);// 给信号量解锁 ++
int sem_post(sem_t *sem);
  • 信号量基本操作

在这里插入图片描述

6.2 生产者消费者信号量模型

在这里插入图片描述

/*信号量实现 生产者 消费者问题*/
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>#define NUM 5               int queue[NUM];                                     // 全局数组实现环形队列
sem_t blank_number, product_number;                 // 空格子信号量, 产品信号量void *producer(void *arg) {int i = 0;while (1) {sem_wait(&blank_number);                    // 生产者将空格子数--,为0则阻塞等待queue[i] = rand() % 1000 + 1;               // 生产一个产品printf("----Produce---%d\n", queue[i]);        sem_post(&product_number);                  // 将产品数++i = (i+1) % NUM;                            // 借助下标实现环形sleep(rand()%1);}
}void *consumer(void *arg) {int i = 0;while (1) {sem_wait(&product_number);                  // 消费者将产品数--,为0则阻塞等待printf("-Consume---%d\n", queue[i]);queue[i] = 0;                               // 消费一个产品 sem_post(&blank_number);                    // 消费掉以后,将空格子数++i = (i+1) % NUM;sleep(rand()%3);}
}int main(int argc, char *argv[]) {pthread_t pid, cid;sem_init(&blank_number, 0, NUM);                // 初始化空格子信号量为5, 线程间共享 -- 0sem_init(&product_number, 0, 0);                // 产品数为 0pthread_create(&pid, NULL, producer, NULL);pthread_create(&cid, NULL, consumer, NULL);pthread_join(pid, NULL);pthread_join(cid, NULL);sem_destroy(&blank_number);sem_destroy(&product_number);return 0;
}
$ gcc sem.c -o sem -pthread
$ ./sem
----Produce---384
-Consume---384
----Produce---916
-Consume---916
...

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

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

相关文章

小白继续深入学习C++

第1节 指针的基本概念 1、变量的地址&#xff1a; 变量是内存地址的简称&#xff0c;在C中&#xff0c;每定义一个变量&#xff0c;系统就会给变量分配一块内存&#xff0c;内存是有地址的。 C用运算符&获取变量在内存中的起始地址。 语法&#xff1a; &变…

零基础Linux_9(进程)环境变量+进程地址空间+进程创建fork

目录 1. 环境变量 1.1 环境变量基本概念 1.2 环境变量PATH 1.3 环境变量HOME和SHELL 1.4 获取环境变量&#xff08;main函数参数&#xff09; 1.4.1 main函数第三个参数 1.4.2 设置普通变量和环境变量 1.4.3 main函数前两个参数 2. 进程地址空间 2.1 验证进程地址空…

PHP8的数据封装(数据隐藏)-PHP8知识详解

面向对象的特点之一就是封装性&#xff0c;也就是数据封装&#xff0c;也被称为数据隐藏。 php8通过限制访问权限来实现数据的封装性&#xff0c;这里用到了public、private、protected、static和final几个关键字。下面来介绍前3个。 1.、public&#xff08;公共成员&#xf…

CSP-J第二轮试题-2021年-3题

文章目录 参考&#xff1a;总结 [CSP-J 2021] 网络连接题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 样例 #3样例输入 #3样例输出 #3 样例 #4样例输入 #4样例输出 #4 提示答案1答案2 现场真题注意事项 参考&#xff1a; https://www…

2022年全球一次能源消费量:石油消耗量持续增加达190.69百亿亿焦耳,亚太地区消费量居首位[图]

一次性能源是指从自然界取得未经改变或转变而直接利用的能源。如原煤、原油、天然气、水能、风能、太阳能、海洋能、潮汐能、地热能、天然铀矿等。一次性能源又分为可再生能源和不可再生能源&#xff0c;前者指能够重复产生的天然能源&#xff0c;包括太阳能、风能、潮汐能、地…

C/C++与汇编混合编程

1. C/C调用汇编 C/C想调用汇编代码必须要注意名称修饰的问题 名称修饰(name decoration): 一种标准的C/C编译技术, 通过添加字符来修改函数名, 添加的字符指明了每个函数参数的确切类型。主要是为了支持函数重载, 但对于汇编来说其问题在于, C/C编译器让链接器去找被修饰过的名…

cadence SPB17.4 S032 - 使用room来放置元件

文章目录 cadence SPB17.4 S032 - 使用room来放置元件概述笔记在orcad中设置子原理图的ROOM号码在空的Allegro工程中, 放入板框在allegro中建立room备注补充 - ROOM还得留着END cadence SPB17.4 S032 - 使用room来放置元件 概述 如果在allegro中直接手工或自动放置元件, 放好…

LeNet网络复现

文章目录 1. LeNet历史背景1.1 早期神经网络的挑战1.2 LeNet的诞生背景 2. LeNet详细结构2.1 总览2.2 卷积层与其特点2.3 子采样层&#xff08;池化层&#xff09;2.4 全连接层2.5 输出层及激活函数 3. LeNet实战复现3.1 模型搭建model.py3.2 训练模型train.py3.3 测试模型test…

Linux系统编程系列之进程间通信-信号量组

一、什么是信号量组 信号量组是信号量的一种&#xff0c; 是system-V三种IPC对象之一&#xff0c;是进程间通信的一种方式。 二、信号量组的特性 信号量组不是用来传输数据的&#xff0c;而是作为“旗语”&#xff0c;用来协调各进程或者线程工作的。信号量组可以一次性在其内…

【LeetCode】滑动窗口妙解无重复字符的最长子串

Problem: 3. 无重复字符的最长子串 文章目录 思路算法原理分析暴力枚举 哈希表滑动窗口 复杂度Code 思路 首先我们来分析一下本题的思路 如果读者有看过 长度最小的子数组 的话就可以清楚这个子串其实和子数组是一个道理&#xff0c;都是 连续的一段区间但是呢它们本质上还是存…

应用架构的演进:亚马逊的微服务实践

当你在亚马逊上购物时,或许不会想到,你看到的这个购物网站,其背后技术架构经历了什么样的变迁与升级。 还记得上世纪 90 年代,那个只卖书的网上书店吗?那时的亚马逊,不过是一个架构简单的网站,所有的功能都堆积在一个庞大的软件堡垒里。随着更多业务的增加、更新和迭代,这个软…

【小程序 - 基础】页面导航、页面事件、生命周期、WXS脚本_04

目录 一、页面导航 1. 什么是页面导航 2. 小程序中实现页面导航的两种方式 2.1 声明式导航 2.1.1 导航到 tabBar 页面 2.1.2 导航到非 tabBar 页面 2.1.3 后退导航 2.2 编程式导航 2.2.1 导航到 tabBar 页面 2.2.2 导航到非 tabBar 页面 2.2.3 后退导航 2.3. 导航…

从1开始的Matlab(快速入门)

MATLAB软件版本&#xff1a;MATLAB R2016b 本文是博主从零开始学Matlab的记录&#xff0c;适合第一次接触Matlab的同学阅读。 一、基础介绍 1.1界面认识 1.2变量命名 注&#xff1a;Matlab中的注释 %% 独占一行的注释&#xff08;有上下横线分割&#xff09; % 普通注释 …

C语言:选择+编程(每日一练Day9)

目录 选择题&#xff1a; 题一&#xff1a; 题二&#xff1a; 题三&#xff1a; 题四&#xff1a; 题五&#xff1a; 编程题&#xff1a; 题一&#xff1a;自除数 思路一&#xff1a; 题二&#xff1a;除自身以外数组的乘积 思路二&#xff1a; 本人实力有限可能对…

深入理解 Swift 新并发模型中 Actor 的重入(Reentrancy)问题

问题现象 我们知道,Swift 5.5 引入的新并发模型极大简化了并行逻辑代码的开发,更重要的是:使用新并发模型中的 Actor 原语可以大大降低并发数据竞争的可能性。 不过,即便 Actor 有如此神奇之功效,它也不是“万能药”,仍不能防止误用带来的问题。比如:Actor 重入(Reen…

283. 多边形,《算法竞赛进阶指南》,

283. 多边形 - AcWing题库 “多边形游戏”是一款单人益智游戏。 游戏开始时&#xff0c;给定玩家一个具有 N 个顶点 N 条边&#xff08;编号 1∼N&#xff09;的多边形&#xff0c;如图 1 所示&#xff0c;其中 N4 每个顶点上写有一个整数&#xff0c;每个边上标有一个运算符…

数据分析方法:RFM模型

一、RFM基本原理 RFM是三个单词的缩写&#xff1a; 最近一次消费时间&#xff08;Recency&#xff09;&#xff0c;取数的时候一般取最近一次消费记录到当前时间的间隔&#xff0c;比如&#xff1a;7天、30天、90天未到店消费&#xff1b;直观上&#xff0c;一个用户太久不到…

MySql进阶篇---006:存储引擎,索引,SQL优化,视图、存储过程、变量、流程控制、游标、存储函数、触发器

1. 存储引擎 1.1 MySQL体系结构 1).连接层 最上层是一些客户端和链接服务&#xff0c;包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念&#xff0c;为通过认证…

Redis与分布式-主从复制

接上文 常用中间件-OAuth2 1.主从复制 启动两个redis服务器。 修改第一个服务器地址 修改第二个redis 然后分别启动 redis-server.exe redis.windows.conf) 查看当前服务器的主从状态&#xff0c;打开客户端&#xff1a;输入info replication命令来查看当前的主从状态&am…

2023/10/1 -- ARM

今日任务&#xff1a;select实现服务器并发 ser.c&#xff1a; #include <myhead.h>#define ERR_MSG(msg) do{\printf("%d\n",__LINE__);\perror(msg);\ }while(0)#define PORT 8888#define IP "192.168.1.5"int main(int argc, const char *argv[…