Linux 多线程 - 线程异步与同步机制
Linux 多线程 - 线程异步与同步机制
I. 同步机制
线程间的同步机制主要包括三个:
- 互斥锁:
以排他的方式,防止共享资源被并发访问;
互斥锁为二元变量, 状态为0-开锁、1-上锁;
开锁必须由上锁的线程执行,不受其它线程干扰.- 条件变量:
满足某个特定条件时,可通过条件变量通知其它线程do-something;
必须与互斥锁*联合使用,单独无法执行.读写锁:
针对多读者,少写者的情况设定
- 允许多读,但此时不可写;
- 唯一写,此时不可读.
函数的头文件为:
#include <phtread.h>
1. 互斥锁
操作流程:
- I. 创建互斥锁
- II. 申请锁:若可用,立刻占用;否则,阻塞等待
- III. do-something
- IV. 释放锁
- V. 销毁锁
以下是互斥锁的基本操作函数:
功能 | 函数 | 参数 | 返回值 | 说明 |
---|---|---|---|---|
初始化锁 | int pthread_mutex_init( pthread_mutex_t *mutex, const pthread_mutexattr_t *attr) |
1. mutex: 欲建立的互斥锁 2.attr:属性,一般为NULL |
成功:0 失败:非零值 |
|
阻塞申请锁 | int pthread_mutex_lock( pthread_mutex_t *mutex) |
mutex:互斥锁 | 成功:0 失败:非零值 |
若未申请到, 阻塞等待 |
非阻塞申请 | int pthread_mutex_trylock( pthread_mutex_t *mutex) |
mutex:互斥锁 | 成功:0 失败:非零值 |
若未申请到, 返回错误 |
释放锁 | int pthread_mutex_unlock( pthread_mutex_t *mutex) |
mutex:互斥锁 | 成功:0 失败:非零值 |
|
销毁锁 | int pthread_mutex_destroy( pthread_mutex_t *mutex) |
mutex:互斥锁 | 成功:0 失败:非零值 |
2. 条件变量
注意,条件变量必须与互斥锁共同使用;
以下是条件变量的基本操作函数:
功能 | 函数 | 参数 | 返回值 | 说明 |
---|---|---|---|---|
初始化锁 | int pthread_cond_init( pthread_cond_t *cond, const pthread_condattr_t *attr) |
1. cond: 欲建立的条件变量 2.attr:属性,一般为NULL |
成功:0 失败:非零值 |
|
等待条件变量 | int pthread_cond_wait( pthread_cond_t *cond, pthread_mutex_t *mutex) |
1.cond:条件变量 2.mutex:互斥锁 |
成功:0 失败:非零值 |
阻塞等待 隐含释放申请到的互斥锁 |
限时等待条件变量 | int pthread_cond_timewait( pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *time) |
3.time:等待过期的绝对时间 从1970-1-1:0:0:0起 |
成功:0 失败:非零值 |
struct timespec{long ts_sec; long ts_nsec} |
单一通知 | int pthread_cond_signal( pthread_cond_t *cond) |
cond:条件变量 | 成功:0 失败:非零值 |
唤醒等待cond的第一个线程 隐含获取需要的互斥锁 |
广播通知 | int pthread_cond_broadcast( pthread_cond_t *cond) |
cond:条件变量 | 成功:0 失败:非零值 |
唤醒所有等待cond的线程 隐含获取需要的互斥锁 |
销毁条件变量 | int pthread_cond_destroy( pthread_cond_t *cond) |
cond:条件变量 | 成功:0 失败:非零值 |
3. 读写锁
读写基本原则:
- 若当前线程读数据,则允许其他线程读数据,但不允许写
- 若当前线程写数据,则不允许其他线程读、写数据
以下是基本的操作:
功能 | 函数 | 参数 | 返回值 | 说明 |
---|---|---|---|---|
初始化锁 | int pthread_rwlock_init( pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr) |
1. rwlock: 欲建立的读写锁 2.attr:属性,一般为NULL |
成功:0 失败:非零值 |
|
阻塞申请读锁 | int pthread_rwlock_rdlock( pthread_rwlock_t *rwlock) |
rwlock:读写锁 | 成功:0 失败:非零值 |
若未申请到, 阻塞等待 |
非阻塞申请 | int pthread_rwlock_tryrdlock( pthread_rwlock_t *rwlock) |
rwlock:读写锁 | 成功:0 失败:非零值 |
若未申请到, 返回错误 |
阻塞申请写锁 | int pthread_rwlock_wrlock( pthread_rwlock_t *rwlock) |
rwlock:读写锁 | 成功:0 失败:非零值 |
若未申请到, 阻塞等待 |
非阻塞申请写锁 | int pthread_rwlock_trywrlock( pthread_rwlock_t *rwlock) |
rwlock:读写锁 | 成功:0 失败:非零值 |
若未申请到, 返回错误 |
释放锁 | int pthread_mutex_unlock( pthread_rwlock_t *rwlock) |
rwlock:读写锁 | 成功:0 失败:非零值 |
|
销毁锁 | int pthread_rwlock_destroy( pthread_rwlock_t *rwlock) |
rwlock:读写锁 | 成功:0 失败:非零值 |
4. 线程信号量
线程信号量类似进程的信号量,主要是使得多个线程访问共享资源时,顺序互斥访问。
与互斥锁的区别在于:
- 互斥锁:只有一个bool类型的值,只允许2个线程进行排队;
- 信号量:允许多个线程共同等待一个共享资源
函数如下:
#include <semaphore.h>
功能 | 函数 | 参数 | 返回值 | 说明 |
---|---|---|---|---|
创建信号量 | int sem_init(sem_t *sem, int pshared, unsigned int value) |
1. sem:信号量地址; 2. pshared:是(!=0)否(0)为共享信号量 3. value:信号量初值 |
0: 成功 -1: 失败 |
|
P操作(阻塞) | int sem_wait(sem_t *sem) | sem:信号量地址 | 0: 成功 -1: 失败 |
|
P操作(非阻塞) | int sem_trywait(sem_t *sem) | sem:信号量地址 | 0: 成功 -1: 失败 |
|
P操作(时间) | int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout) |
1. sem:信号量地址 2. abs_timeout:超时时间 |
0: 成功 -1: 失败 |
struct timespec 见下面 |
V操作 | int sem_post(sem_t *sem) | sem:信号量地址 | 0: 成功 -1: 失败 |
|
获取信号量值 | int sem_getvalue(sem_t *sem, int *sval) | 1. sem:信号量地址 2. sval: 将信号量值放到该地址 |
0: 成功 -1: 失败 |
|
删除信号量 | int sem_destroy(sem_t *sem) | sem:信号量地址 | 0: 成功 -1: 失败 |
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
II. 异步机制 - 信号
线程的异步机制只有信号
,类似于线程的信号。
线程信号具备以下特点
- 任何线程都可以向其它线程(同一进程下)发送信号;
- 每个线程都具备自己独立的信号屏蔽集,不影响其它线程;
- 线程创建时,不继承原线程的信号屏蔽集;
- 同进程下,所有线程共享对某信号的处理方式,即一个设置,所有有效;
- 多个线程的程序,向某一个线程发送终止信号,则整个进程终止
信号的基本操作如下:
功能 | 函数 | 参数 | 返回值 | 说明 |
---|---|---|---|---|
安装信号 | sighandler_t signal( int signum, sighandler_t handler) |
1.signum:信号值 2.handler:信号操作 |
详情参见: http://www.cnblogs.com/Jimmy1988/p/7575103.html |
|
发送信号 | int pthread_kill( pthread_t threadid, int signo |
1.threadid: 目标线程id 2.signo:信号值 |
成功:0 失败:非零值 |
若signo=0, 检测该线程是否存在, 不发送信号 |
设置屏蔽集 | pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset) |
1.how:如何更改信号掩码 2.newmask:新的信号屏蔽集 3.原信号屏蔽集 |
成功:0 失败:非零值 |
how值: 1.SIG_BLOCK:添加新掩码 2.SIG_UNBLOCK:删除新掩码 3.SIG_SETMASK:设置新掩码完全替换旧值 |
也可以参考这篇博客:https://www.cnblogs.com/coding-my-life/p/4782529.html
III、示例代码
1.同步机制:
1). 互斥锁:
两个线程:
- 读线程:从
stdin
中读取数据,并存储- 写线程:从存储buffer中读取数据并显示
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#define SIZE 128
pthread_mutex_t mutex;
int EXIT = 0;
char word[SIZE];
void * child(void *arg)
{
while(1)
{
while(strlen(word) == 0)
usleep(100);
pthread_mutex_lock(&mutex);
printf("The input words: %s\n", word);
pthread_mutex_unlock(&mutex);
if(strcmp("end\n", word) == 0)
{
printf("The process end\n");
EXIT = 1;
break;
}
memset(word, '\0', SIZE);
}
return ;
}
int main()
{
//1. create the lock
pthread_mutex_init(&mutex, NULL);
//2.create a new thread
pthread_t tid;
pthread_create(&tid, NULL, (void *)*child, NULL);
//3. Input words
while(EXIT == 0)
{
if(strlen(word)!=0)
usleep(100);
//add the lock
else
{
pthread_mutex_lock(&mutex);
printf("Input words: ");
fgets(word, SIZE, stdin);
pthread_mutex_unlock(&mutex);
}
}
pthread_join(tid, NULL);
printf("The child has joined\n");
pthread_mutex_destroy(&mutex);
return 0;
}
2). 条件变量:
生产者和消费者问题:
- 生产者:
向仓库生产数据(大小可任意设定),当满时,阻塞等待仓库有空闲(由消费者消费完后通知)- 消费者:
从仓库读数据,若仓库为空,则阻塞等待,当生产者再次生产产品后通知
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define SIZE 2
int Data[SIZE];
typedef struct
{
pthread_mutex_t lock;
pthread_cond_t notFull;
pthread_cond_t notEmpty;
int read_point;
int write_point;
}sCOND;
sCOND *pCondLock;
void init(void)
{
//memset(pCondLock, 0, sizeof(sCOND));
//1.Create a mutex lock
pthread_mutex_init(&pCondLock->lock, NULL);
//2.Create two condition variable
pthread_cond_init(&pCondLock->notFull, NULL);
pthread_cond_init(&pCondLock->notEmpty, NULL);
//set the read and write point 0
pCondLock->read_point = 0;
pCondLock->write_point = 0;
}
int put(int data)
{
//obtain the mutex lock
pthread_mutex_lock(&pCondLock->lock);
//check the global variable Data full or not
while((pCondLock->write_point+1)%SIZE == pCondLock->read_point)
{
printf("The buf is full, waitting for not_full signal\n");
pthread_cond_wait(&pCondLock->notFull, &pCondLock->lock);
}
//write the data to buffer
Data[pCondLock->write_point] = data;
pCondLock->write_point++;
if(pCondLock->write_point == SIZE)
pCondLock->write_point = 0;
//unlock the mutex lock
pthread_mutex_unlock(&pCondLock->lock);
//wake up the not_empty signal
pthread_cond_signal(&pCondLock->notEmpty);
return 0;
}
int get(int *data)
{
//obtain the mutex lock
pthread_mutex_lock(&pCondLock->lock);
//check the global variable Data empty or not
while(pCondLock->write_point == pCondLock->read_point)
{
printf("The buf is empty, waitting for not_empty signal\n");
pthread_cond_wait(&pCondLock->notEmpty, &pCondLock->lock);
}
//read the data from buffer
*data = Data[pCondLock->read_point];
pCondLock->read_point++;
if(pCondLock->read_point == SIZE)
pCondLock->read_point = 0;
//wake up the not_empty signal
pthread_cond_signal(&pCondLock->notFull);
pthread_mutex_unlock(&pCondLock->lock);
return *data;
}
void *produce(void)
{
int times=0;
//1. first 5 times, every second write a data to buffer
for(times=0; times < 5; times++)
{
sleep(1);
put(times+1);
printf("Input date=%d\n", times+1);
}
//2. last 5 times, every 3 seconds write a data to buffer
for(times = 5; times < 10; times++)
{
sleep(3);
put(times+1);
printf("Input date=%d\n", times+1);
}
}
void *consume(void)
{
int times=0;
int data=0;
//10 times, every 2 seconds read the buffer
for(times = 0; times < 10; times++)
{
sleep(2);
data = get(&data);
printf("The data is %d\n", data);
}
}
int main()
{
pthread_t tid1, tid2;
pCondLock = malloc(sizeof(sCOND));
memset(pCondLock, '\0', sizeof(sCOND));
//1.init the struct of sCondLock
init();
//2. start two threads
pthread_create(&tid1, NULL, (void*)*produce, NULL);
pthread_create(&tid2, NULL, (void*)*consume, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
free(pCondLock);
return 0;
}
3). 读写锁:
四个线程:两读两写;
多进程可同时读,但此时不可写;
只有一个线程可写,其它线程等待该线程写完后执行响应的读/写操作
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#define BUF_SIZE 128
char buf[BUF_SIZE];
pthread_rwlock_t rwlock;
int time_to_exit = 0;
void *read_first(void *arg);
void *read_second(void *arg);
void *write_first(void *arg);
void *write_second(void *arg);
int main()
{
pthread_t tid_rd1, tid_rd2;
pthread_t tid_wr1, tid_wr2;
//1.create a read-write-lock
int ret = pthread_rwlock_init(&rwlock, NULL);
if(ret != 0)
{
perror("pthread_rwlock_init");
exit(EXIT_FAILURE);
}
//2. Create the read and write threads
ret = pthread_create(&tid_rd1, NULL, (void *)*read_first, NULL);
if(ret != 0)
{
perror("pthread_create");
exit(EXIT_FAILURE);
}
ret = pthread_create(&tid_rd2, NULL, (void *)*read_second, NULL);
if(ret != 0)
{
perror("pthread_create");
exit(EXIT_FAILURE);
}
ret = pthread_create(&tid_wr1, NULL, (void *)*write_first, NULL);
if(ret != 0)
{
perror("pthread_create");
exit(EXIT_FAILURE);
}
ret = pthread_create(&tid_wr2, NULL, (void *)*write_second, NULL);
if(ret != 0)
{
perror("pthread_create");
exit(EXIT_FAILURE);
}
//3. wait for the threads finish
pthread_join(tid_rd1, NULL);
pthread_join(tid_rd2, NULL);
pthread_join(tid_wr1, NULL);
pthread_join(tid_wr2, NULL);
//4. delete the read-write-lock
pthread_rwlock_destroy(&rwlock);
return 0;
}
/***************************************************/
// Write threads
void *write_first(void *arg)
{
while(!time_to_exit)
{
sleep(5);
//1. get the read-lock
pthread_rwlock_wrlock(&rwlock);
printf("\nThis is thread write_first!\n");
printf("Pls input the string: ");
fgets(buf, BUF_SIZE, stdin);
pthread_rwlock_unlock(&rwlock);
}
printf("Exit the write_first!\n");
pthread_exit(0);
}
void *write_second(void *arg)
{
while(!time_to_exit)
{
sleep(10);
//1. get the read-lock
pthread_rwlock_wrlock(&rwlock);
printf("\nThis is thread write_second!\n");
printf("Pls input the string: ");
fgets(buf, BUF_SIZE, stdin);
pthread_rwlock_unlock(&rwlock);
}
printf("Exit the write_second!\n");
pthread_exit(0);
}
//-----2. read the threads
void *read_first(void *arg)
{
while(1)
{
sleep(5);
pthread_rwlock_rdlock(&rwlock);
printf("\nThis is thread read_first\n");
//if write an string of "end"
if(!strncmp("end", buf, 3))
{
printf("Exit the read_first!\n");
break;
}
//if nothing in the BUFFER
while(strlen(buf) == 0)
{
pthread_rwlock_unlock(&rwlock);
sleep(2);
pthread_rwlock_rdlock(&rwlock);
}
//output the string in BUFFER
printf("The string is: %s\n", buf);
pthread_rwlock_unlock(&rwlock);
}
pthread_rwlock_unlock(&rwlock);
//make the exit true
time_to_exit = 1;
pthread_exit(0);
}
void *read_second(void *arg)
{
while(1)
{
sleep(4);
pthread_rwlock_rdlock(&rwlock);
printf("\nThis is thread read_second\n");
//if write an string of "end"
if(!strncmp("end", buf, 3))
{
printf("Exit the read_second!\n");
break;
}
//if nothing in the BUFFER
while(strlen(buf) == 0)
{
pthread_rwlock_unlock(&rwlock);
sleep(2);
pthread_rwlock_rdlock(&rwlock);
}
//output the string in BUFFER
printf("The string is: %s\n", buf);
pthread_rwlock_unlock(&rwlock);
}
pthread_rwlock_unlock(&rwlock);
//make the exit true
time_to_exit = 1;
pthread_exit(0);
}
2. 异步机制 - 信号:
本程序包括两个线程:
- 线程1安装SIGUSR1,阻塞除SIGUSR2外的所有信号;
- 线程2安装SIGUSR2,不阻塞任何信号
操作流程:
- 1- 线程1、2安装信号;
- 2- 主线程发送SIGUSR1和SIGUSR2至线程1和线程2;
- 3- 线程1接收到除SIGUSR2之外的信号,阻塞不执行;当收到SIGUSR2后,执行对应操作;
- 4- 线程2接收到SIGUSR1和SIGUSR2后,分别执行对应操作
- 5- 主线程发送SIGKILL信号,结束整个进程
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
void *th_first(void *arg);
void *th_second(void *arg);
pthread_t tid1, tid2;
void handler(int signo)
{
printf("In handler: tid_%s, signo=%d\n", ((pthread_self() == tid1)?"first":"second"), signo);
}
int main()
{
int ret = 0;
//1. create first thread
ret = pthread_create(&tid1, NULL, (void *)*th_first, NULL);
if(0 !=ret)
{
perror("pthread_create");
exit(EXIT_FAILURE);
}
//2. create second thread
ret = pthread_create(&tid2, NULL, (void *)*th_second, NULL);
if(0 !=ret)
{
perror("pthread_create");
exit(EXIT_FAILURE);
}
sleep(2);
//3. send the signal of SIG_USER1 and SIG_USER2 to thread_first
ret = pthread_kill(tid1, SIGUSR1);
if(0 !=ret)
{
perror("pthread_kill");
exit(EXIT_FAILURE);
}
ret = pthread_kill(tid1, SIGUSR2);
if(0 !=ret)
{
perror("pthread_kill");
exit(EXIT_FAILURE);
}
//4. send the signal of SIG_USER1 and SIG_USER2 to thread_second_
sleep(1);
ret = pthread_kill(tid2, SIGUSR1);
if(0 !=ret)
{
perror("pthread_kill");
exit(EXIT_FAILURE);
}
ret = pthread_kill(tid2, SIGUSR2);
if(0 !=ret)
{
perror("pthread_kill");
exit(EXIT_FAILURE);
}
sleep(1);
//5. send SIGKILL to all threads
ret = pthread_kill(tid1, SIGKILL);
if(0 !=ret)
{
perror("pthread_kill");
exit(EXIT_FAILURE);
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
void *th_first(void *arg)
{
//1. Add SIGUSR1 signal
signal(SIGUSR1, handler);
//2. Set the sinagl set
sigset_t set;
sigfillset(&set); //init set to be full, include all signal
sigdelset(&set, SIGUSR2); //delete the SIGUSR2 from the set variable
pthread_sigmask(SIG_SETMASK, &set, NULL); //set the current mask set to be defined set variable
//3. Circular wait the signal
int i;
for(i=0; i<5; i++)
{
printf("\nThis is th_first, tid=%#x\n ", pthread_self());
pause();
}
}
void *th_second(void *arg)
{
usleep(100);
//1. Add the signal of SIGUSR2
signal(SIGUSR2, handler);
//2. Circular wait the signal
int i;
for(i=0; i<5; i++)
{
printf("\nThis is th_second, tid=%#x\n", pthread_self());
pause();
}
}
Linux 多线程 - 线程异步与同步机制的更多相关文章
- Java多线程——线程之间的同步
Java多线程——线程之间的同步 摘要:本文主要学习多线程之间是如何同步的,如何使用volatile关键字,如何使用synchronized修饰的同步代码块和同步方法解决线程安全问题. 部分内容来自以 ...
- C# 多线程、异步、同步之间的联系与区别
C# 多线程.异步.同步之间的联系与区别 假设这样一个例子: 我想炒五样菜,但是只有两个炉子可以用,只能同时炒两样. 炉子就是线程,那同步跟异步怎么解释比较好? 同时炒是不是算异步? 如果是的话,那什 ...
- 线程池,多线程,线程异步,同步和死锁,Lock接口
线程池 线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源. 除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源.线程 ...
- Linux多线程——使用互斥量同步线程
前文再续,书接上一回,在上一篇文章: Linux多线程——使用信号量同步线程中,我们留下了一个如何使用互斥量来进行线程同步的问题,本文将会给出互斥量的详细解说,并用一个互斥量解决上一篇文章中,要使用两 ...
- Linux多线程--使用互斥量同步线程【转】
本文转载自:http://blog.csdn.net/ljianhui/article/details/10875883 前文再续,书接上一回,在上一篇文章:Linux多线程——使用信号量同步线程中, ...
- linux C 多线程/线程池编程 同步实例
在多线程.线程池编程中经常会遇到同步的问题. 1.创建线程 函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, ...
- vc++高级班之多线程篇[7]---线程间的同步机制②
//示例代码: CStringArray g_ArrString; UINT __cdecl ThreadProc(LPVOID lpParameter) { int startIdx = (int ...
- vc++高级班之多线程篇[6]---线程间的同步机制①
①.线程同步的必要性: int g_Num = 0; UINT __cdecl ThreadProc(LPVOID lpParameter) { for (int idx = 0; idx &l ...
- java面试必问:多线程的实现和同步机制,一文帮你搞定多线程编程
前言 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线 ...
随机推荐
- C#使用MemoryStream类读写内存
MemoryStream和BufferedStream都派生自基类Stream,因此它们有很多共同的属性和方法,但是每一个类都有自己独特的用法.这两个类都是实现对内存进行数据读写的功能,而不是对持久性 ...
- 【python练习题】程序1
#题目:有1.2.3.4个数字,能组成多少个互不相同且无重复数字的三位数?都是多少? count = 0 for i in range(1,5): for j in range(1,5): for k ...
- ☆ [HDU4825] Xor Sum「最大异或和(Trie树)」
传送门:>Here< 题意:给出一个集合,包含N个数,每次询问给出一个数x,问x与集合中的一个数y异或得到最大值时,y是多少? 解题思路 由于N,M非常大,暴力显然不行.抓住重点是异或,所 ...
- Failed to load package MonoAndroidDesignerPackage
from : https://developercommunity.visualstudio.com/content/problem/160124/failed-to-load-package-mon ...
- haar的简单应用(2)
上次对图片进行了人脸识别,这次对摄像头捕获的内容进行识别 直接写注释来解释 import cv2 def CatchUsbVideo(window_name, camera_idx): #定义一个函数 ...
- linux环境node服务器配置流程
一. 安装node Node 官网已经把 linux 下载版本更改为已编译好的版本了,我们可以直接下载解压后使用: # wget https://nodejs.org/dist/v10.9.0/nod ...
- Android 播放Gif 动画
在Android 中是不支持直接使用Gif 图片关联播放帧动画,如下动画在Android 中是无法播放的: Android 提供了另外一种解决的办法,就是使用AnimationDrawable 这一函 ...
- 【Luogu4707】重返现世(min-max容斥)
[Luogu4707]重返现世(min-max容斥) 题面 洛谷 求全集的\(k-max\)的期望 题解 \(min-max\)容斥的证明不难,只需要把所有元素排序之后考虑组合数的贡献,容斥系数先设出 ...
- 【转】分享两个基于MDK IDE的调试输出技巧
我们在STM32开发调试过程中,常常需要做些直观的输出,如果手头没有相关的设备或仪器,我们可以使用 IDE自带的工具.这里分享两个基于MDK IDE的调试输出技巧. 一.使用其自带的逻辑分析仪查看波 ...
- luogu5012 水の数列 (并查集+线段树)
如果我们能求出来每个区间个数的最大分值,那就可以用线段树维护这个东西 然后出答案了 然后这个的求法和(luogu4269)Snow Boots G非常类似,就是我们把数大小排个序,每次都拿<=x ...