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. 异步机制 - 信号

线程的异步机制只有信号,类似于线程的信号。

线程信号具备以下特点

    1. 任何线程都可以向其它线程(同一进程下)发送信号;
    1. 每个线程都具备自己独立的信号屏蔽集,不影响其它线程;
    1. 线程创建时,不继承原线程的信号屏蔽集;
    1. 同进程下,所有线程共享对某信号的处理方式,即一个设置,所有有效;
    1. 多个线程的程序,向某一个线程发送终止信号,则整个进程终止

信号的基本操作如下:

功能 函数 参数 返回值 说明
安装信号 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 多线程 - 线程异步与同步机制的更多相关文章

  1. Java多线程——线程之间的同步

    Java多线程——线程之间的同步 摘要:本文主要学习多线程之间是如何同步的,如何使用volatile关键字,如何使用synchronized修饰的同步代码块和同步方法解决线程安全问题. 部分内容来自以 ...

  2. C# 多线程、异步、同步之间的联系与区别

    C# 多线程.异步.同步之间的联系与区别 假设这样一个例子: 我想炒五样菜,但是只有两个炉子可以用,只能同时炒两样. 炉子就是线程,那同步跟异步怎么解释比较好? 同时炒是不是算异步? 如果是的话,那什 ...

  3. 线程池,多线程,线程异步,同步和死锁,Lock接口

    线程池 线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源. 除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源.线程 ...

  4. Linux多线程——使用互斥量同步线程

    前文再续,书接上一回,在上一篇文章: Linux多线程——使用信号量同步线程中,我们留下了一个如何使用互斥量来进行线程同步的问题,本文将会给出互斥量的详细解说,并用一个互斥量解决上一篇文章中,要使用两 ...

  5. Linux多线程--使用互斥量同步线程【转】

    本文转载自:http://blog.csdn.net/ljianhui/article/details/10875883 前文再续,书接上一回,在上一篇文章:Linux多线程——使用信号量同步线程中, ...

  6. linux C 多线程/线程池编程 同步实例

    在多线程.线程池编程中经常会遇到同步的问题. 1.创建线程 函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, ...

  7. vc++高级班之多线程篇[7]---线程间的同步机制②

    //示例代码: CStringArray g_ArrString; UINT __cdecl ThreadProc(LPVOID lpParameter) {  int startIdx = (int ...

  8. vc++高级班之多线程篇[6]---线程间的同步机制①

    ①.线程同步的必要性:   int g_Num = 0; UINT __cdecl ThreadProc(LPVOID lpParameter) {  for (int idx = 0; idx &l ...

  9. java面试必问:多线程的实现和同步机制,一文帮你搞定多线程编程

    前言 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线 ...

随机推荐

  1. solr单机版搭建

    需要把solr服务器安装到linux环境: 第一步:安装linux.jdk.tomcat. [root@bogon ~]# ll total 8044 -rw-r--r--. 1 root root ...

  2. Installing Office Online Server for SharePoint 2016

    Office Online Server is the next version of the Office Web Apps, which allows your users to view and ...

  3. 实现纯英文string的逆序输出

      第一种方法: using namespace std; void Reverse(string &a) { int n = a.size(); char b; ;i<n/;i++) ...

  4. linux查找符合条件的文件并删除

    找到根目录下所有的以test开头的文件并把查找结果当做参数传给rm -rf命令进行删除: 1.find / -name “test*” |xargs rm -rf 2.find / -name “te ...

  5. BZOJ1565[NOI2009]植物大战僵尸——最大权闭合子图+拓扑排序

    题目描述 Plants vs. Zombies(PVZ)是最近十分风靡的一款小游戏.Plants(植物)和Zombies(僵尸)是游戏的主角,其中Plants防守,而Zombies进攻.该款游戏包含多 ...

  6. Educational Codeforces Round 53 (Rated for Div. 2) C. Vasya and Robot

    题意:给出一段操作序列 和目的地 问修改(只可以更改 不可以删除或添加)该序列使得最后到达终点时  所进行的修改代价最小是多少 其中代价的定义是  终点序号-起点序号-1 思路:因为代价是终点序号减去 ...

  7. 洛谷P2670扫雷游戏题解

    题目 这道题是一个简单的模拟搜索题,可以把每个雷的位置都记作1. 这样就可记录出数字啦 #include<iostream> #include<cstring> using n ...

  8. C#中 const 和 readonly 的区别

    C#中 const 和 readonly 的区别 来源 https://www.cnblogs.com/gsk99/archive/2008/10/10/1308299.html http://dev ...

  9. EtherType :以太网类型字段及值

    Ethernet II即DIX 2.0:Xerox与DEC.Intel在1982年制定的以太网标准帧格式.Cisco名称为:ARPA Ethernet II类型以太网帧的最小长度为64字节(6+6+2 ...

  10. Vue中的slot内容分发

    ①概述: 简单来说,假如父组件需要在子组件内放一些DOM,那么这些DOM是显示.不显示.在哪个地方显示.如何显示,就是slot分发负责的活. ②默认情况下 父组件在子组件内套的内容,是不显示的. 例如 ...