Linux C 多线程

为什么会有线程?  

      ————————>>>>        进程实现多任务的缺点

  1. 进程间切换的计算机资源开销很大,切换效率非常低
  2. 进程间数据共享的开销也很大

线程进程关系

  • 线程是进程的一个执行单元,是进程内的调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程
  • 同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
  • 进程退出,进程中所有线程全部退出;
  • 一个进程崩溃后,不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
  • 线程不可能完全替代进程;
  • 线程拥有独立的属性;

线程是任务调度和执行的基本单位
 

线程的特点

线程不安全,不稳定,不健壮————(一个线程的释放可能会影响其他线程)

线程切换的开销很低————(实质就是函数的切换)

线程通信机制简单(但不安全)————(访问全局变量)

线程操作

线程函数(不是OS提供,不是系统调用API,而是线程库libpthread.a/.so,库函数则可以跨平台)

线程库和函数手册的安装

sudo apt-get install glibc-doc :安装线程库
sudo apt-get install manpages-posix-dev:安装线程库的函数手册

 

线程创建

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

执行顺序:先创建的先执行

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
void *read_mouse(void *ptr)
{int mouse;while (1){read(*((int *)ptr), &mouse, sizeof(int));printf("mouse = %d\n", mouse);}
}
void *read_keyboard(void *ptr)
{char buffer[1024] = "0";while (1){read(0, buffer, sizeof(buffer));printf("%s\n", buffer);memset(buffer, 0, sizeof(buffer));}
}int main(int argc, char **argv)
{pthread_t id1;pthread_t id2;int fd = open("/dev/input/mouse0", O_RDONLY);if (fd < 0){perror("fd mouse is error");exit(-1);}if (pthread_create(&id1, NULL, read_mouse, (void *)(&fd)) < 0) // 传递给线程的参数{perror("pthread_creat 1 error");exit(-1);}if (pthread_create(&id2, NULL, read_keyboard, NULL) < 0){perror("pthread_creat 2 error");exit(-1);}pause(); // 若无pause,则主进程执行完,所有的线程会结束;所以需要return 0;
}

封装:

void *read_mouse(void *ptr)
{int mouse;while (1){int fd = open("/dev/input/mouse0", O_RDONLY);if (fd < 0){perror("fd mouse is error");exit(-1);}read(fd, &mouse, sizeof(int));printf("mouse = %d\n", mouse);}
}
void *read_keyboard(void *ptr)
{char buffer[1024] = "0";while (1){read(0, buffer, sizeof(buffer));printf("%s\n", buffer);memset(buffer, 0, sizeof(buffer));}
}int main(int argc, char **argv)
{pthread_t id[2];void *(*ptr[2])(void *);ptr[0] = read_keyboard;ptr[1] = read_mouse;for (int i = 0; i < 2; ++i){pthread_create(id + i, NULL, *(ptr+i), NULL);}pause(); // 若无pause,则主进程执行完,所有的线程会结束;所以需要return 0;
}

线程可以访问全局数据区

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>char buffer[1024];void *write_data(void *)
{while (1){scanf("%s", buffer);}
}
void *read_data(void *)
{while (1){if (strlen(buffer) != 0){printf("%s\n", buffer);memset(buffer,0,sizeof(buffer));}sleep(3);}
}
int main(int argc, char **argv)
{pthread_t id[2];void *(*func[2])(void *);func[0] = write_data;func[1] = read_data;for (int i = 0; i < 2; ++i){pthread_create(id + i, NULL, *(func + i), NULL);}pause();return 0;
}

线程退出

1.被动退出:

主线程关闭子线程:
int pthread_cancel(pthread_t thread);
1)功能
当次线程是死循环时,可以调动这个函数主动取消该线程
2)返回值
成功返回0,失败返回非零错误号。
2〉参数
thread:要取消线程的ID号

int main(int argc, char **argv)
{pthread_t id[2];void *(*func[2])(void *);func[0] = write_data;func[1] = read_data;for (int i = 0; i < 2; ++i){pthread_create(id + i, NULL, *(func + i), NULL);}sleep(5);pthread_cancel(id[1]);//五秒钟后,次线程被主线程取消了,则不再能读取到buffer内数据pause();return 0;
}

2.主动退出

2.1 void pthread_exit(void *retval);函数用于退出当前线程,并可选择返回一个指定的值(用的多)

2.2 return ;     也是直接退出(用得少)

void *read_data(void *)
{while (1){if (strlen(buffer) != 0){if (strcmp(buffer,"exit")==0)//如果读到了exit就退出{pthread_exit(NULL); // 读取到exit,就会主动的退出线程}printf("%s\n", buffer);memset(buffer, 0, sizeof(buffer));}}
}void *read_data(void *)
{while (1){if (strlen(buffer) != 0){if (strcmp(buffer,"exit")==0)//如果读到了exit就退出{return NULL; // 读取到exit,就会主动的退出线程}printf("%s\n", buffer);memset(buffer, 0, sizeof(buffer));}}
}

3.注册线程退出处理函数

pthread_cleanup_push

pthread_cleanup_pop

必须成对出现,且不能在{ }的语句中间,(因为他们是宏定义,各包含一个括号

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>char buffer[1024];
void *read_exit_handler(void *arg)
{printf("%s\n", (char *)arg);
}
void *write_data(void *)
{while (1){scanf("%s", buffer);}
}void *read_data(void *)
{pthread_cleanup_push(read_exit_handler, (void *)"cleannup done ");while (1){if (strlen(buffer) != 0){if (strcmp(buffer, "exit") == 0) // 如果读到了exit就退出{pthread_exit(NULL); // 可以调用退出清理函数(出栈)// return NULL;        // 不可以调用退出清理函数(出栈)}printf("%s\n", buffer);memset(buffer, 0, sizeof(buffer));}sleep(1); // 无限的循环,会导致无法调用pthread_cancel(id[1]);来调用退出处理函数!!!!!所以给每次循环睡眠一秒}pthread_cleanup_pop(0); // 非零值表示会执行清理函数,零值表示不执行清理
//------------------------即使pthread_cleanup_pop(0),但如果还是遇到了      pthread_exit(NULL)和pthread_cancel    还是会执行清理函数-------------------------------------
}
int main(int argc, char **argv)
{pthread_t id[2];void *(*func[2])(void *);func[0] = write_data;func[1] = read_data;for (int i = 0; i < 2; ++i){pthread_create(id + i, NULL, *(func + i), NULL);}sleep(3);pthread_cancel(id[1]); // 五秒钟后,次线程被主线程取消了,则不再能读取到buffer内数据pause();return 0;
}

多个线程的退出清理函数执行顺序:

char buffer[1024];
void *read_exit_handler(void *arg)
{printf("%s\n", (char *)arg);
}
void *read_exit_handler2(void *arg)
{printf("%s 2\n", (char *)arg);
}
void *write_data(void *)
{while (1){scanf("%s", buffer);}
}void *read_data(void *)
{pthread_cleanup_push(read_exit_handler, (void *)"cleannup done ");pthread_cleanup_push(read_exit_handler2, (void *)"cleannup done ");printf("hello world\n");pthread_cleanup_pop(!0);pthread_cleanup_pop(!0); // 非零值表示会执行清理函数,零值表示不执行清理}
int main(int argc, char **argv)
{pthread_t id[2];void *(*func[2])(void *);func[0] = write_data;func[1] = read_data;for (int i = 0; i < 2; ++i){pthread_create(id + i, NULL, *(func + i), NULL);}pause();return 0;
}

线程等待

1.等待的目的:

1.1保证线程的退出顺序:保证一个线程退出并且回收资源后允许下一个线程退出
1.2回收线程退出时的资源情况:保证当前线程退出后,创建的新线程不会复用刚才退出线程的地址空间
1.3
获得新线程退出时的结果是否正确的退出返回值,这个有点类似回收僵尸进程的wait,保证不会发生内存泄露等问题
 

2.pthread_join(id[1], &ret); // 会阻塞,直到线程结束,才会执行下面的代码;)

线程状态

        1.线程资源为什么不采用进程退出后一起回收?

        2.Linux下把线程分为可结合态和分离态

可结合态:(默认的状态) 这种状态下的线程是能被其它进程回收其资源或杀死,资源只能通过pthread_join来回收(不能自己释放,进程主动回收)

分离态:这种状态下的线程是不能够被其它进程回收其资源或杀死;存储资源在它终止时由系统自动释放

        3.如何避免多线程退出导致的内存泄漏

3.1 可结合线程都显示调用pthread_join回收

3.2 将其变成分离态的线程:

【      

3.2.1:int pthread_detach(pthread_t thread);        (不阻塞)
功能:        如果次线程的资源不希望别人调用pthread_join函数来回收的话,而是希望自己在结束时自动回收资源的话,就可以调用这个函教
这个函数的功能就是分离次线程,让次线程在结束时自动回收资源
返回值:        成功返回0,失败返回错误号
参数:        thread:你要分离的那个次线程的TID

int main(int argc, char **argv)
{pthread_t id[2];void *(*func[2])(void *);func[0] = write_data;func[1] = read_data;for (int i = 0; i < 2; ++i){if (pthread_create(id + i, NULL, *(func + i), NULL) < 0){perror("pthread creat error");exit(-1);}pthread_detach(id[i]); // 设置为分离态,  资助回收资源,减少内存泄漏//此时也不需要pthread_join来回收资源了    
}pause();return 0;
}

3.2.2 修改属性

线程实现,向毅个文件里分别写如“hhhhhwwwww”和“helloworld”

线程同步(难点)

       

         1.进程vs线程

进程:进程空间天然是独立的,因此进程间资源的保护是天然的(现成的),需要重点关心的进程间的通信
线程:多线程天然的共享进程空间,因此线程数据共享是天然的(现成的),需要重点关心的是资源的保护

        2.线程的资源保护机制

2.1互斥锁

定义一个互斥锁(变量)

初始化互斥锁:预设互斥锁的初始值

1.直接赋值

2.初始化互斥锁的函数:int pthread_mutex_init ( pthread_mutex_t *restrict mutex,const pthread mutexattr_t *restrict attr);
功能:初始化定义的互斥锁
什么是初始化:就是设置互斥锁所需要的值。
返回值:总是返回0,所以这个函数不需要进行出错处理。
参数:~~~

【在C语言中,初始化静态全局变量时,只能使用常量表达式。PTHREAD_MUTEX_INITIALIZER`是一个宏,它并不是常量表达式,因此不能直接用于初始化静态全局变量。

如果你想在主函数内初始化互斥锁变量`mutex1`,可以使用`pthread_mutex_init`函数来实现。你可以将以下代码添加到主函数的开头,用于初始化互斥锁:
pthread_mutex_init(&mutex1, NULL);

这样,你就可以在主函数中正确地初始化互斥锁,并在其他线程中使用它。

对于你的代码,你可以这样修改:
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;

或者使用`pthread_mutex_init`函数进行初始化:
pthread_mutex_t mutex1;
pthread_mutex_init(&mutex1, NULL);

这样修改后,`mutex1`将在主函数内正确地进行初始化。】

加锁、解锁

pthread_mutex_lock(&mutex1);————pthread_mutex_trylock( &mutex)(非阻塞加锁)

pthread_mutex_unlock(&mutex1);

进程退出时销毁互斥锁

void handler(int sig)
{pthread_mutex_destroy(&mutex1);
}
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>pthread_mutex_t mutex1= PTHREAD_MUTEX_INITIALIZER;//定义一个互斥锁变量 
//*****不能在主函数内调用这句话来初始化静态全局变量
int fd;
void handler(int sig)
{pthread_mutex_destroy(&mutex1);
}
void *write1(void *arg)
{while (1){pthread_mutex_lock(&mutex1);write(fd, "hhhhh", 5);write(fd, "wwwww", 5);write(fd, "\n", 1);pthread_mutex_unlock(&mutex1);}
}
void *write2(void *arg)
{while (1){pthread_mutex_lock(&mutex1);write(fd, "hello", 5);write(fd, "world", 5);write(fd, "\n", 1);pthread_mutex_unlock(&mutex1);}
}
int main(int argc, char **argv)
{// pthread_mutex_init(&mutex1, NULL);pthread_t id[2];void *(*pfunc[2])(void *);pfunc[0] = write1;pfunc[1] = write2;fd = open("a.txt", O_RDWR | O_CREAT | O_TRUNC, 0655);if (fd < 0){perror("fd error");exit(-1);}for (int i = 0; i < 2; ++i){if (pthread_create(&id[i], NULL, pfunc[i], NULL) < 0){perror("pthread error");exit(-1);}pthread_detach(id[i]);}pause();signal(SIGINT, handler);return 0;
}/--------------------------                 -----------------------------/
void *write1(void *arg)
{while (1){if (pthread_mutex_trylock(&mutex1) == 0)//非阻塞的上锁,如果已经上锁,则返回0;{write(fd, "hhhhh", 5);write(fd, "wwwww", 5);write(fd, "\n", 1);pthread_mutex_unlock(&mutex1);}else{printf("trylock is busy\n");}}
}

注意事项:

1.凡是访问进程的全局变量,都要加锁;

2.涉及到共享资源的访问,必须上锁

【对于一个功能实现中,并不涉及访问共享资源的代码段,尽量不要放在上锁和解锁中间】

2.2线程信号量

2.2.1.定义信号量集合


2.2.2.初始化集合中的每个信号量


2.2.3.p、v操作

2.2.4.进程结束时,删除线程信号量集


 

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <semaphore.h>sem_t sem1; // 1.定义信号量 (数组)
int fd;
pthread_t id[2];
void handler(int sig) // 4.注册的信号处理函数:用以删除信号量
{sem_destroy(&sem1);
}
void *write1(void *arg)
{while (1){sem_wait(&sem1); // 3.p操作write(fd, "hhhhh", 5);write(fd, "wwwww", 5);write(fd, "\n", 1);sem_post(&sem1); // 3.v操作}
}
void *write2(void *arg)
{while (1){sem_wait(&sem1);write(fd, "hello", 5);write(fd, "world", 5);write(fd, "\n", 1);sem_post(&sem1);}
}
int main(int argc, char **argv)
{sem_init(&sem1, 0, 1); // 2.初始化信号量void *(*pfunc[2])(void *);pfunc[0] = write1;pfunc[1] = write2;fd = open("c.txt", O_RDWR | O_CREAT | O_TRUNC, 0655);if (fd < 0){perror("fd error");exit(-1);}for (int i = 0; i < 2; ++i){if (pthread_create(&id[i], NULL, pfunc[i], NULL) < 0){perror("pthread error");exit(-1);}pthread_detach(id[i]);}pause();signal(SIGINT, handler);return 0;
}

实现google面试题

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <signal.h>
#include <pthread.h>
#include <sys/stat.h>
#include <errno.h>int fd[4];         // 四个文件描述符
sem_t sem_abcd[4]; // 含有四个信号量的数组
pthread_t id[4];   // 四个线程id的数组
void *write1(void *arg)
{int flag = 0;while (1){sem_wait(&sem_abcd[0]);for (int i = 0; i < 4; ++i){if (flag != 0 || i <= 0){write(fd[i], "A", 1);}}flag = 1;sleep(1);sem_post(&sem_abcd[1]);}
}
void *write2(void *arg)
{int flag = 0;while (1){sem_wait(&sem_abcd[1]);for (int i = 0; i < 4; ++i){if (flag != 0 || i <= 1){write(fd[i], "B", 1);}}flag = 1;sleep(1);sem_post(&sem_abcd[2]);}
}
void *write3(void *arg)
{int flag = 0;while (1){sem_wait(&sem_abcd[2]);for (int i = 0; i < 4; ++i){if (flag != 0 || i <= 2){write(fd[i], "C", 1);}}flag = 1;sleep(1);sem_post(&sem_abcd[3]);}
}
void *write4(void *arg)
{int flag = 0;while (1){sem_wait(&sem_abcd[3]);for (int i = 0; i < 4; ++i){if (flag != 0 || i <= 3){write(fd[i], "D", 1);}}flag = 1;sleep(1);sem_post(&sem_abcd[0]);}
}int main(int argc, char **argv)
{char temp[1] = "A";for (int i = 0; i < 4; ++i){temp[0] = temp[0] + i;if ((fd[i] = open(temp, O_CREAT | O_RDWR | O_TRUNC, 0655)) < 0) // 打开四个文件{perror("open error");exit(-1);}temp[0] = 'A';}void *(*pfunc[4])(void *) = {// 函数指针数组write1,write2,write3,write4,};for (int i = 0; i < 4; ++i){if (i == 0){sem_init(&sem_abcd[i], 0, 1); // 先初始化第一个线程的信号量————置为1}else{sem_init(&sem_abcd[i], 0, 0); // 其他线程的信号量全关闭————置为0}if (pthread_create(&id[i], NULL, pfunc[i], NULL) < 0) // 线程创建{perror("pthread creat error");exit(-1);}pthread_detach(id[i]); // 分离态}pause();return 0;
}

功能封装:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <signal.h>
#include <pthread.h>
#include <sys/stat.h>
#include <errno.h>int fd[4];         // 四个文件描述符
sem_t sem_abcd[4]; // 含有四个信号量的数组
pthread_t id[4];   // 四个线程id的数组
int flag = 0;void *write_demo(void *arg)
{char c[1] = "A";int num = *(int *)arg;c[0] = 'A' + num;while (1){sem_wait(&sem_abcd[num]);for (int i = 0; i < 4; ++i){if (flag >= 4 || i <= num){write(fd[i], c, 1);}}flag++;sleep(1);sem_post(&sem_abcd[(num + 1) % 4]);}
}int main(int argc, char **argv)
{char temp[1] = "A";int demo[4] = {0, 1, 2, 3};for (int i = 0; i < 4; ++i){temp[0] = temp[0] + i;if ((fd[i] = open(temp, O_CREAT | O_RDWR | O_TRUNC, 0655)) < 0) // 打开四个文件{perror("open error");exit(-1);}temp[0] = 'A';}for (int i = 0; i < 4; ++i){if (i == 0){sem_init(&sem_abcd[i], 0, 1); // 先初始化第一个线程的信号量————置为1}else{sem_init(&sem_abcd[i], 0, 0); // 其他线程的信号量全关闭————置为0}if (pthread_create(&id[i], NULL, write_demo, (void *)(demo + i)) < 0) // 线程创建{perror("pthread creat error");exit(-1);}pthread_detach(id[i]); // 分离态}pause();return 0;
}

2.3条件变量

作用:多线程配合工作时,当线程检测到某条件不满足时就休眠,直到别的线程将条件准备好然后通过条件变量将其唤醒

【需要和互斥锁配合】

1.定义条件变量:pthread_cond_t;

2.初始化:

1.pthread_cond_init(pthread_cond_t *restrict cond,const thread_condattr_t * restrict attr)

2.赋值初始化:pthread_cond_t cond=PTHREAD_COND_INITLIALIZER

3.等待条件的函数

pthread_cond_wait(&cond, &mutex);                 // 不满足条件则阻塞等待,并释放互斥锁

4.满足条件,通知阻塞的线程

pthread_cond_signal(&cond);                                 // 满足条件,则通知因cond这一条件变量而阻塞等待的线程,
或者: pthread_cond_broadcast(&cond);唤醒所有睡眠的进程

5.销毁条件变量和互斥锁

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <pthread.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>pthread_t id[2];
int val = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t cond; // 1.定义条件变量
// 条件变量的初始化可以      1.在主函数内调用pthread_cond——init初始化;  2.也可以用PTHREAD_COND_INITIALIZER赋值;void handler_exit(int sig)
{pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond); // 5.注册退出处理函数,删除条件变量printf(" pthread_cond is cancel\n");
}
void *fun1(void *arg)
{while (1){pthread_mutex_lock(&mutex);val++;if (val == 2){                               // 通知线程2打印pthread_cond_signal(&cond); // 4.满足条件,则通知因cond这一条件变量而阻塞等待的线程,// pthread_cond_broadcast(&cond);唤醒所有睡眠的进程}pthread_mutex_unlock(&mutex);sleep(1);}
}
void *fun2(void *arg)
{while (1){pthread_mutex_lock(&mutex);if (val != 2){pthread_cond_wait(&cond, &mutex); // 3.不满足条件则阻塞等待,并释放互斥锁}printf("val == 2\n");val = 0;pthread_mutex_unlock(&mutex);}
}
int main(int argc, char **argv)
{signal(SIGINT, handler_exit);pthread_cond_init(&cond, NULL); // 2.初始化条件变量void *(*pfun[2])(void *arg) = {fun1, fun2};for (int i = 0; i < 2; ++i){if (pthread_create(&id[i], NULL, pfun[i], NULL) < 0){perror("pthread_creat error");exit(-1);}pthread_detach(id[i]);}pause();return 0;
}

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

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

相关文章

在PHP8中对数组进行排序-PHP8知识详解

在php8中&#xff0c;提供了丰富的排序函数&#xff0c;可以对数组进行排序操作。常见的排序函数如下几个&#xff1a;sort() 函数、rsort() 函数、asort() 函数、arsort() 函数、ksort() 函数、krsort() 函数、natsort()函数和natcascsort()函数。 1、sort() 函数&#xff1a;…

从0到1学会Git(第三部分):Git的远程仓库链接与操作

写在前面:前面两篇文章我们已经学会了git如何在本地进行使用&#xff0c;这篇文章将讲解如何将本地的git仓库和云端的远程仓库链接起来并使用 为什么要使用远程仓库:因为我们需要拷贝我们的代码给别人以及进行协同开发&#xff0c;就需要有一个云端仓库进行代码的存储和同步&a…

【Java 基础篇】Java List 使用指南:深入解析列表操作

Java 是一门强大的编程语言&#xff0c;拥有丰富的数据结构和集合类&#xff0c;其中之一就是 List 列表。List 是 Java 集合框架中的一个重要接口&#xff0c;它允许我们以有序、可重复的方式存储一组元素。本篇博客将从基础到高级&#xff0c;详细介绍 Java 中的 List 接口以…

Qt配置使用MSVC编译器

Qt配置使用MSVC编译器_qt msvc-CSDN博客注意:Qt支持的MSVC就是2017和2015&#xff0c;所以vs也要下载2017&#xff0c;不要直接用最新的&#xff0c;安装路径都用默认的。程序运行失败时可以尝试windeployqt拷贝库文件到本地&#xff0c;然后有可能就能运行了。VS官网下载Visua…

苹果电脑Mac系统运行速度又卡又慢是怎么回事?

通常大家处理Mac运行速度慢的方法不是重启就是清空废纸篓&#xff0c;但是这两种方法对于Mac提速性能的效果是微之甚微的&#xff0c;想要彻底解决Mac运行速度慢&#xff0c;你应该试试一下三种方法~ 1、清理磁盘空间 硬盘空间过少是Mac运行变慢很大的一个因素&#xff0c;各…

反向动力学Ik学习

参考文章&#xff1a;&#xff08;非本人原创&#xff09; 英文原文&#xff1a;Inverse Kinematics Techniques in Computer Graphics: A Survey (andreasaristidou.com) 知乎翻译文章&#xff1a; 【游戏开发】逆向运动学&#xff08;IK&#xff09;详解 - 知乎 (zhihu.co…

【Linux】线程的概念

文章目录 &#x1f4d6; 前言1. 线程的引入1.1 执行流&#xff1a;1.2 线程的创建&#xff1a;1.3 线程的等待&#xff1a; 2. 查看线程2.1 链接线程库&#xff1a;2.2 ps -aL&#xff1a; 3. 页表的认识3.1 二级页表&#xff1a;3.2 页表的实际大小&#xff1a; 4. 再看线程4.…

WPF——Control与Template理解

文章目录 一、前言二、控件三、模板3.1 DataTemplate3.2 ControlTemplate3.3 ContentPresenter 四、结语 一、前言 最近又翻看了下刘铁猛的《深入浅出WPF》&#xff0c;发现对模板章节中的部分内容有了更深的体会&#xff0c;所以写篇文扯扯。 文章标题是Control与Template&a…

Linux vim的常见基本操作

目录 vim是一款多模式的编辑器 命令模式下&#xff1a; 用小写英文字母「h」、「j」、「k」、「l」&#xff0c;分别控制光标左、下、上、右移一格 gg&#xff1a;定位到代码第一行 nshiftg 定位到任意一行/最后一行 「 $ 」&#xff1a;移动到光标所在行的结尾 「 ^ 」&…

python基础开发篇3——线上环境部署Django项目

文章目录 一、基本了解二、打包本地项目三、服务器环境准备四、安装web服务4.1 使用uwsgi代理4.2 使用nginx代理&#xff08;推荐&#xff09; 五、部署daphne 一、基本了解 部署思路&#xff1a; Nginx服务接收浏览器的动态请求&#xff0c;再通过uwsgi模块将请求转发给uwsgi服…

利用html+css+js实现回到顶部小功能

本章教程&#xff0c;主要是实现一个网站中比较常见的小功能&#xff0c;这个功能就是回到顶部。 功能描述&#xff1a;当浏览器右侧的滚动条&#xff0c;滑动到某个位置的时候&#xff0c;显示回到顶部图标&#xff0c;回到顶部之后&#xff0c;图标作隐藏处理&#xff0c;本文…

c++数据类型

基本数据类型简介 位、字节和内存寻址 最小的内存单位是二进制数字&#xff08;也称为位&#xff09;&#xff0c;它可以保存 0 或 1 的值。你在现代计算机体系结构中&#xff0c;每个位都没有自己唯一的内存地址。这是因为内存地址的数量有限&#xff0c;并且很少需要逐位访…

目标检测笔记(十四): 使用YOLOv8完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)

文章目录 一、目标检测介绍二、YOLOv8介绍三、源码获取四、环境搭建4.1 环境检测 五、数据集准备六、 模型训练6.1 方式一6.2 方式二6.3 针对其他任务 七、模型验证八、模型测试九、模型转换9.1 转onnx9.1.1 方式一 9.2 转tensorRT9.2.1 trtexec9.2.2 代码转换9.2.3 推理代码 一…

《JDK17新特性和代码案例演示》

《JDK17新特性和代码案例演示》 &#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全…

酷开系统音乐频道,用音乐治愈你!

音乐作为娱乐生活中的一部分&#xff0c;它可以起到调节心情让身体放松的作用&#xff0c;同时还可以舒缓压力&#xff0c;给大脑一个休息的时间。有句话说得好&#xff1a;“耳机是人类的避难所&#xff0c;音乐是心脏的救命丸”。音乐是一种疗愈身心的存在&#xff0c;耳机线…

最新版WPS 2023 加载Zotero方法

安装wps2019vba.exe&#xff0c;获取链接&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1eeoc6Tmwyzxh3n1MFQTVeA 提取码&#xff1a;6431 –来自百度网盘超级会员V8的分享 打开WPS的工具的加载项 添加文件路径&#xff0c;我的在&#xff1a; C:\Users\Administrat…

Vue3+Ts中使用Jquery

1、安装jquery&#xff1a;npm i jquery --save 2、在vue.config.js文件中添加如下代码&#xff1a; const { defineConfig } require(vue/cli-service) const webpack require(webpack)module.exports defineConfig({configureWebpack: {plugins: [// 配置jQuerynew webp…

【C++ 学习 ㉑】- 详解 map 和 set(上)

目录 一、C STL 关联式容器 二、pair 类模板 三、set 3.1 - set 的基本介绍 3.2 - set 的成员函数 3.1.1 - 构造函数 3.1.2 - 迭代器 3.1.3 - 修改操作 3.1.4 - 其他操作 四、map 4.1 - map 的基本介绍 4.2 - map 的成员函数 4.2.1 - 迭代器 4.2.2 - operator[] …

解决出现的java: 无法访问org.springframework.boot.SpringApplication问题~

错误描述如下所示&#xff1a; 错误原因&#xff1a;版本号匹配不一致导致的&#xff0c;61.0对应jdk17&#xff0c;52.0对应jdk8。 而我本地的java为java8&#xff0c;因此需要降低版本&#xff0c;即可解决该问题 <groupId>org.springframework.boot</groupId>…

EndNote21 | 安装及库的创建

EndNote21 | 安装及库的创建 一、EndNote21安装二、EndNote21库的创建 一、EndNote21安装 软件安装界面&#xff0c;双击“EndNote 21.exe”程序&#xff1b; 图1 安装软件界面点击next&#xff0c;选择30天试用&#xff0c;点击next&#xff1b; 图2 安装过程点击next&…