• 嵌入式系统多线程学习笔记


    1) 标题最简单的多线程程序,不存在互斥

    
    #include 
    #include 
    #include 
    #include      /*Unix标准函数定义*/
    void * thread1(void *arg)
    {
       while(1)
       {
          printf("hello world!\n");
          sleep(1);
        }
    }
    int main()
    {
       pthread_t id;
       if(pthread_create(&id,NULL,thread1,NULL)!=0)
       {
          perror("pthread create error!\n");
          exit(1);
       }
       while(1)
       {
          
       }
       //pause(); 
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    注意编译的时候必须增加 –lpthread 选项,否则是不能编译通过的,因为在linux中默认编译时不引用多线程的库的,这样才能在编译的时候调用多线程的库。
    main:main.o
    $(CC) main.o -o thread -lpthread
    main.o:main.c
    $(CC) -c main.c -o main.o -lpthread
    clean:
    $(RM) *.o thread

    2) 使用信号量进行多线程编程 (适用于无规则,不控制,仅保证不冲突即可,互不干扰)

    #include
    int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
    该函数初始化一个互斥量,第一个参数 *mutex是改互斥量指针,第二个参数 *restrict attr为控制互斥量的属性,一般为NULL。当函数成功后会返回0,代表初始化互斥量成功。
      初始化互斥量也可以调用宏来快速初始化,代码如下:
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;
    互斥量加锁/解锁
    #include
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    成功:返回0
    lock函数与unlock函数分别为加锁解锁函数,只需要传入已经初始化好的pthread_mutex_t互斥量指针。成功后会返回0。
      当某一个线程获得了执行权后,执行lock函数,一旦加锁成功后,其余线程遇到lock函数时候会发生阻塞,直至获取资源的线程执行unlock函数后。unlock函数会唤醒其他正在等待互斥量的线程。
      特别注意的是,当获取lock之后,必须在逻辑处理结束后执行unlock,否则会发生死锁现象!导致其余线程一直处于阻塞状态,无法执行下去。在使用互斥量的时候,尤其要注意使用pthread_cancel函数,防止发生死锁现象!
    int pthread_cancel(pthread_t thread);函数用于线程被动退出,该函数传入一个tid号,会强制退出该tid所指向的线程,若成功执行会返回0
    线程的创建
    #include
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
       *thread:用来保存新建线程的线程号
       *attr:表示线程的属性,一般传入NULL表示默认属性
       *(start_routine) (void ):一个函数指针,就是线程执行的函数。这个函数返回值为void,形参为void
       *arg:表示为向线程处理函数传入的参数,若不传入,可用NULL填充

    
    #include
    #include
    #include
    #include
    #include	/*信号量操作要包含的库*/
    #include
    #include
    #include
    #include   /*使用信号量PV要包含的库 */
    struct  message
    {
        int fd;
    };
    sem_t sem[1];
    void * thread1(void *arg)
    {
        struct message msg =  *((struct message * )arg);
        int fd= msg.fd;
        while(1)
    {
    /* */
            sem_wait(&sem[0]);
            printf("write 111\n");
            write(fd,"111\n",4);
            sem_post(&sem[0]);
            sleep(1);
        }
    }
    void * thread2(void *arg)
    {
         struct message msg =  *((struct message * )arg);
        int fd= msg.fd;
        while(1)
        {
            sem_wait(&sem[0]);
             printf("write 222\n");
            write(fd,"222\n",4);
            sem_post(&sem[0]);
            sleep(1);
        }
    }
    void *thread3(void *arg)
    {
         struct message msg =  *((struct message * )arg);
        int fd= msg.fd;
        while(1)
        {
            sem_wait(&sem[0]);
            printf("write 333\n");
            write(fd,"333\n",4);
            sem_post(&sem[0]);
            sleep(1);
        }
    }
    int main()
    {
        pthread_t id1;
        pthread_t id2;
        pthread_t id3;
        struct message msg;
        int fd;
        fd=open("./a.txt",O_RDWR | O_CREAT | O_APPEND,0644);
        if(fd<0)
        {
            perror("open file error!");
            exit(1);
        }
    msg.fd=fd;
    /*初始化一个信号量
    第一个参数*sem传入sem_t类型指针
    第二个参数pshared传入0代表线程控制,否则为进程控制
     第三个参数value表示信号量的初始值,0代表阻塞,1代表运行
     待初始化结束信号量后,若执行成功会返回0
    */
        sem_init(&sem[0],0,1);
         
        if(pthread_create(&id1,NULL,thread1,(void *)(&msg)) != 0)
        {
            perror("thread create error!\n");
            exit(1);
        }
        if(pthread_create(&id2,NULL,thread2,(void *)(&msg)) != 0)
        {
            perror("thread create error!\n");
            exit(1);
        }
       if(pthread_create(&id3,NULL,thread3,(void *)(&msg)) != 0)
        {
            perror("thread create error!\n");
            exit(1);
        }
        pthread_join(id1,NULL);
        pthread_join(id2,NULL);
        pthread_join(id3,NULL);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97

    3) 多线程互斥操作(适合于主从线程,从线程可有主线程控制的情况,使用信号量控制)

    这个实际上和2)差不多就是指定一个主循环运行一段代码,相当于一个线程,然后在主循环中给出从线程运行的通知,让从线程执行。
    https://blog.csdn.net/weixin_43444989/article/details/123106933
    初始化互斥量
    #include
    int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
      该函数初始化一个互斥量,第一个参数 *mutex是改互斥量指针,第二个参数 *restrict attr为控制互斥量的属性,一般为NULL。当函数成功后会返回0,代表初始化互斥量成功。
      初始化互斥量也可以调用宏来快速初始化,代码如下:
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;
    或者
    pthread_mutex_t mut
    pthread_mutex_init(&mut,NULL);
    互斥量加锁/解锁
    #include
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    成功:返回0
      lock函数与unlock函数分别为加锁解锁函数,只需要传入已经初始化好的pthread_mutex_t互斥量指针。成功后会返回0。
      当某一个线程获得了执行权后,执行lock函数,一旦加锁成功后,其余线程遇到lock函数时候会发生阻塞,直至获取资源的线程执行unlock函数后。unlock函数会唤醒其他正在等待互斥量的线程。
      特别注意的是,当获取lock之后,必须在逻辑处理结束后执行unlock,否则会发生死锁现象!导致其余线程一直处于阻塞状态,无法执行下去。在使用互斥量的时候,尤其要注意使用pthread_cancel函数,防止发生死锁现象!
    int pthread_cancel(pthread_t thread);函数用于线程被动退出,该函数传入一个tid号,会强制退出该tid所指向的线程,若成功执行会返回0
    线程的创建
    #include
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
       *thread:用来保存新建线程的线程号
       *attr:表示线程的属性,一般传入NULL表示默认属性
       *(start_routine) (void ):一个函数指针,就是线程执行的函数。这个函数返回值为void,形参为void
       *arg:表示为向线程处理函数传入的参数,若不传入,可用NULL填充
    程序功能:从主线程中接收键盘输入内容保存在全局变量中,在子线程中将变量中的内容输出

    #include 
    #include 
    #include 
    #include 
    #include 
    static char g_buf[1000];//用来保存键盘输入内容的全局变量
    static sem_t g_sem;
    //也可以使用 pthread_mutex_init(&g_tMutex, NULL); 来初始化互斥量
    static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER;
    static void *my_thread_func (void *data)
    {
    	while (1)
    	{
    		/* 等待通知 */
    		sem_wait(&g_sem);
    		/* 打印 */
    		pthread_mutex_lock(&g_tMutex);
    		printf("recv: %s\n", g_buf);
    		pthread_mutex_unlock(&g_tMutex);
    	}
    	return NULL;
    }
    int main(int argc, char **argv)
    {
    	pthread_t tid;
    	int ret;
    	char buf[1000];
    	//g_sem:sem_t类型变量 0:线程控制 0:信号量的初始值,0代表阻塞 
    	sem_init(&g_sem, 0, 0);
    	
    	/* 1. 创建"接收线程" */
    	//tid:保存新建的线程号 NULL:默认属性 my_thread_func:函数指针 NULL:无参数
    	ret = pthread_create(&tid, NULL, my_thread_func, NULL);
    	if (ret)
    	{
    		printf("pthread_create err!\n");
    		return -1;
    	}
    	/* 2. 主线程读取标准输入, 发给"接收线程" */
    	while (1)
    	{
    		//获取标准输入存入buf
    		fgets(buf, 1000, stdin);
    		//加锁成功会返回0,一旦加锁成功其他线程想给同一个互斥量加锁会发生阻塞
    		pthread_mutex_lock(&g_tMutex);
    		memcpy(g_buf, buf, 1000);
    		//解锁成功会返回0,一旦解锁成功,函数会唤醒其他正在等待互斥量的线程
    		pthread_mutex_unlock(&g_tMutex);
    		/* 通知接收线程 */
    		sem_post(&g_sem);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    4) 使用条件变量控制线程

    创建和销毁条件变量
    #include
    // 初始化条件变量
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);//cond_attr通常为NULL
    // 销毁条件变量
    int pthread_cond_destroy(pthread_cond_t *cond);
    这些函数成功时都返回0
    等待条件变量
    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t mutex);
      这需要结合互斥量一起使用,示例代码如下:
    pthread_mutex_lock(&g_tMutex);
    pthread_cond_wait(&g_tConVar, &g_tMutex); // 如果条件不满足则,会unlock g_tMutex
    // 条件满足后被唤醒,会lock g_tMutex
    /
    操作临界资源 */
    pthread_mutex_unlock(&g_tMutex);
      pthread_cond_wait总和一个互斥锁结合使用。在调用pthread_cond_wait前要先获取锁,因为pthread_cond_wait函数执行时先自动释放指定的锁,然后等待条件变量的变化,在函数调用返回之前,自动将指定的互斥量重新锁住。
    通知条件变量
    int pthread_cond_signal(pthread_cond_t *cond);
      pthread_cond_signal函数只会唤醒一个等待cond条件变量的线程,示例代码如下:
    pthread_cond_signal(&g_tConVar);
      pthread_cond_signal通过条件变量cond发送消息,若多个消息在等待,它只唤醒一个
      调用pthread_cond_signal后要立刻释放互斥锁,因为pthread_cond_wait的最后一步是要将指定的互斥量重新锁住,如果pthread_cond_signal之后没有释放互斥锁,pthread_cond_wait仍然要阻塞。

    #include 
    #include 
    #include 
    #include 
    #include 
    static char g_buf[1000];
    static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER;
    //也可以使用pthread_cond_init(g_tConVar,NULL);来初始化条件变量
    static pthread_cond_t  g_tConVar = PTHREAD_COND_INITIALIZER;
    static void *my_thread_func (void *data)
    {
    	while (1)
    	{
    		pthread_mutex_lock(&g_tMutex);
    		/*
    			pthread_cond_wait函数先释放g_tMutex
    			等待条件变量g_tConVar变化后在函数返回之前重新锁定g_tMutex
    		*/
    		pthread_cond_wait(&g_tConVar, &g_tMutex);	
    		/* 打印 */
    		printf("recv: %s\n", g_buf);
    		pthread_mutex_unlock(&g_tMutex);
    	}
    	return NULL;
    }
    int main(int argc, char **argv)
    {
    	pthread_t tid;
    	int ret;
    	char buf[1000];
    	
    	/* 1. 创建"接收线程" */
    	ret = pthread_create(&tid, NULL, my_thread_func, NULL);
    	if (ret)
    	{
    		printf("pthread_create err!\n");
    		return -1;
    	}
    	/* 2. 主线程读取标准输入, 发给"接收线程" */
    	while (1)
    	{
    		fgets(buf, 1000, stdin);
    		pthread_mutex_lock(&g_tMutex);
    		memcpy(g_buf, buf, 1000);
    		pthread_cond_signal(&g_tConVar); /* 通知接收线程 */
    		//调用pthread_cond_signal后要立刻释放互斥锁
    		pthread_mutex_unlock(&g_tMutex);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    5) 向线程传递参数-整型值

    pthread_create()的最后一个参数的为void类型的数据,表示可以向线程传递一个void数据类型的参数,线程的回调函数中可以获取该参数,下面的例子举例了如何向线程传入变量地址与变量值。

    #include 
    #include 
    #include 
    #include 
    void *fun1(void *arg)
    {
    	//(int *)arg:	a的值
    	//arg			:a的地址
    	printf("%s:arg = %d Addr = %p\n",__FUNCTION__,*(int *)arg,arg);
    }
    void *fun2(void *arg)
    {
    	//(int)(long)arg:a的值
    	//arg			:输出是16进制a的值
    	printf("%s:arg = %d Addr = %p\n",__FUNCTION__,(int)(long)arg,arg);
    }
    int main()
    {
    	pthread_t tid1,tid2;
    	int a = 50;
    	//传递变量a的地址
    	int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);
    	if(ret != 0){
    		perror("pthread_create");
    		return -1;
    	}
    	//传递变量a的值,针对不同位数机器,指针对其字数不同,需要int转化为long再转指针,否则可能会发生警告
    	ret = pthread_create(&tid2,NULL,fun2,(void *)(long)a);
    	if(ret != 0){
    		perror("pthread_create");
    		return -1;
    	}
    	sleep(1);
    	printf("%s:a = %d Add = %p \n",__FUNCTION__,a,&a);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    6) 传递结构体参数给线程

    #include 
    #include 
    #include 
    #include 
    #include 
    struct Stu{
    	int Id;
    	char Name[32];
    	float Mark;
    };
    void *fun1(void *arg)
    {
    	struct Stu *tmp = (struct Stu *)arg;
    	printf("%s:Id = %d Name = %s Mark = %.2f\n",__FUNCTION__,tmp->Id,tmp->Name,tmp->Mark);
    	
    }
    int main()
    {
    	pthread_t tid1,tid2;
    	struct Stu stu;
    	stu.Id = 10000;
    	strcpy(stu.Name,"ZhangSan");
    	stu.Mark = 94.6;
    	int ret = pthread_create(&tid1,NULL,fun1,(void *)&stu);
    	if(ret != 0){
    		perror("pthread_create");
    		return -1;
    	}
    	printf("%s:Id = %d Name = %s Mark = %.2f\n",__FUNCTION__,stu.Id,stu.Name,stu.Mark);
    	sleep(1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    7) 线程的退出与回收

    线程的退出情况有三种:第一种是进程结束,进程中所有的线程也会随之结束。第二种是通过函数pthread_exit来主动的退出线程。第三种被其他线程调用pthread_cancel来被动退出。
    线程主动退出
    #include
    void pthread_exit(void retval);
      pthread_exit函数为线程退出函数,在退出时候可以传递一个void
    类型的数据带给主线程,若选择不传出数据,可将参数填充为NULL。
    线程被动退出
    线程被动退出,其他线程使用该函数让另一个线程退出
    #include
    int pthread_cancel(pthread_t thread);
    成功:返回0
      该函数传入一个tid号,会强制退出该tid所指向的线程,若成功执行会返回0。
    线程资源回收(阻塞方式)
    #include
    int pthread_join(pthread_t thread, void **retval);
      该函数为线程回收函数,默认状态为阻塞状态,直到成功回收线程后才返回。第一个参数为要回收线程的tid号,第二个参数为线程回收后接受线程传出的数据。
    线程资源回收(非阻塞方式)
    #define _GNU_SOURCE
    #include
    int pthread_tryjoin_np(pthread_t thread, void **retval);
      该函数为非阻塞模式回收函数,通过返回值判断是否回收掉线程,成功回收则返回0,其余参数与pthread_join一致。
    程序实例
    程序功能:向子线程中传入变量在子线程中自加后子线程主动退出将自加后变量返回给主线程,主线程以阻塞方式回收子线程资源

    #include 
    #include 
    #include 
    #include 
    void *fun1(void *arg)
    {
    	static int tmp = 0;//必须要static修饰,否则pthread_join无法获取到正确值
    	tmp = *(int *)arg;
    	tmp+=100;
    	printf("%s:Addr = %p tmp = %d\n",__FUNCTION__,&tmp,tmp);
    	//线程主动退出
    	pthread_exit((void *)&tmp);
    }
    int main()
    {
    	pthread_t tid1;
    	int a = 50;
    	void *Tmp = NULL;//用来接收线程回收后传递出来的数据
    	int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);
    	if(ret != 0){
    		perror("pthread_create");
    		return -1;
    	}
    	//以阻塞方式完成线程资源回收
    	pthread_join(tid1,&Tmp);
    	printf("%s:Addr = %p Val = %d\n",__FUNCTION__,Tmp,*(int *)Tmp);
    	return 0;
    }
    使用非阻塞方式回收线程
    #define _GNU_SOURCE 
    #include 
    #include 
    #include 
    #include 
    void *fun(void *arg)
    {
    	printf("Pthread:%d Come !\n",(int )(long)arg+1);
    	pthread_exit(arg);
    }
    int main()
    {
    	int ret,i,flag = 0;
    	void *Tmp = NULL;
    	pthread_t tid[3];
    	for(i = 0;i < 3;i++){//创建3个线程
    		ret = pthread_create(&tid[i],NULL,fun,(void *)(long)i);
    		if(ret != 0){
    			perror("pthread_create");
    			return -1;
    		}
    	}
    	while(1){//通过非阻塞方式收回线程,每次成功回收一个线程变量自增,直至3个线程全数回收
    		for(i = 0;i <3;i++){
    			if(pthread_tryjoin_np(tid[i],&Tmp) == 0){
    				printf("Pthread : %d exit !\n",(int )(long )Tmp+1);
    				flag++;	
    			}
    		}
    		if(flag >= 3) break;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    使用被动退出方式在线程1中kill线程2

    #define _GNU_SOURCE 
    #include 
    #include 
    #include 
    #include 
    void *fun1(void *arg)
    {
    	printf("Pthread:1 come!\n");
    	while(1){
    		sleep(1);
    	}
    }
    void *fun2(void *arg)
    {
    	printf("Pthread:2 come!\n");
    	pthread_cancel((pthread_t )(long)arg);//杀死线程1,使之强制退出
    	pthread_exit(NULL);
    }
    int main()
    {
    	int ret,i,flag = 0;
    	void *Tmp = NULL;
    	pthread_t tid[2];
    	ret = pthread_create(&tid[0],NULL,fun1,NULL);
    	if(ret != 0){
    		perror("pthread_create");
    		return -1;
    	}
    	sleep(1);
    	ret = pthread_create(&tid[1],NULL,fun2,(void *)tid[0]);//传输线程1的线程号
    	if(ret != 0){
    		perror("pthread_create");
    		return -1;
    	}
    	while(1){//通过非阻塞方式收回线程,每次成功回收一个线程变量自增,直至2个线程全数回收
    		for(i = 0;i <2;i++){
    			if(pthread_tryjoin_np(tid[i],NULL) == 0){
    				printf("Pthread : %d exit !\n",i+1);
    				flag++;	
    			}
    		}
    		if(flag >= 2) break;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    创作不易,欢迎点赞关注. 下次会发布一个232的测试程序,用多线程方式编写.敬请期待.

  • 相关阅读:
    Cadence OrCAD Capture选择性批量关闭图纸页面功能详细介绍
    E. Hanging Hearts
    【简单讲解下epoll】
    Spring AOP原来是这样实现的
    函数式编程-Stream流(三更草堂)
    【高阶数据结构(八)】跳表详解
    先获取打印的模板,用户选择过的模板通过接口要回显出来,预览只显示key的值,value是前端写死
    屏幕不清晰,可能是你的设置不正确
    第6章_瑞萨MCU零基础入门系列教程之串行通信接口(SCI)
    Hibernate 参数校验之关联数据校验
  • 原文地址:https://blog.csdn.net/mainmaster/article/details/126720224