笔记:https://note.youdao.com/ynoteshare1/index.html?id=e9b66eea3b1a46a3d70b2dc8c35af541&type=note
目录
一.再论进程
1、多进程阻塞式实现同时读取键盘和鼠标
2、使用进程技术的优势
3、进程技术的劣势
4、解决方案就是线程技术
5、线程和进程的区别
二.线程的引入
1、使用线程技术同时读取键盘和鼠标(线程初体验)
创建线程
测试程序
2、linux中的线程简介
3.线程的优点和缺点
优点:
缺点:
三.线程常见函数
1、线程创建与回收
(1)pthread_create 主线程用来创造子线程的
(2)pthread_join 主线程用来等待(阻塞)回收子线程
(3)pthread_detach 主线程用来分离子线程,分离后主线程不必再去回收子线程
2、线程取消
(1)pthread_cancel 一般都是主线程调用该函数去取消(让它赶紧死)子线程
(2)pthread_setcancelstate 子线程设置自己是否允许被取消
(3)pthread_setcanceltype
3、线程函数退出相关
(1)pthread_exit与return退出
(2)pthread_cleanup_push与pthread_cleanup_pop
4、获取线程id
在上一节,我们已经使用过多进程阻塞式实现多去键盘鼠标,这里cp过来。
int main(void) { // 读取鼠标 int fd = -1; char buf[200]; int flag=-1; int ret=-1; fd = open("/dev/input/mouse0", O_RDONLY | O_NONBLOCK); if (fd < 0) { perror("open:"); return -1; } //F_GETFL:获取描述符原有状态给flag的命令,目的是为了保护原有的状态 // STDIN_FILENO:指向标准输入文件的文件描述符0 flag = fcntl(STDIN_FILENO, F_GETFL); flag |= O_NONBLOCK;//将原有文件状态 | 非阻塞标志 fcntl(STDIN_FILENO, F_SETFL, flag);//将修改后的包含了非阻塞标志的新状态重新设置回去
while(1) { //读鼠标 memset(buf, 0, sizeof(buf)); //printf("before 鼠标 read.\n"); ret=read(fd, buf, 50); if(ret>0) //>0,表示已经接收到数据 { printf("鼠标读出的内容是:[%s].\n", buf); } // 读键盘 memset(buf, 0, sizeof(buf)); //printf("before 键盘 read.\n"); ret=read(0, buf, 5); if(ret>0) { printf("键盘读出的内容是:[%s].\n", buf); }
} return 0; } |
这么设置后,鼠标和键盘不再相互阻塞,所以运行这个程序时不必再忌讳谁先输入的问题了,谁先输入都可以。
只是这种非阻塞会导致进程时刻都处在循环的,这种轮询的机制会非常的消耗cpu资源,并不提倡这方法
(1)CPU时分复用,单核心CPU可以实现宏观上的并行
(2)实现多任务系统需求(多任务的需求是客观的)
(1)进程间切换开销大
(2)进程间通信麻烦而且效率低
(1)线程技术保留了进程技术实现多任务的特性。
(2)线程的改进就是在线程间切换和线程间通信上提升了效率。
(3)多线程在多核心CPU上面更有优势。
1、进程是资源竞争的基本单位
2、linux下没有真正意义的线程,因为linux下没有给线程设计专有的结构体,它的线程是用进程模拟的,而它是由多个进程共享一块地址空间而模拟得到的。
3、创建一个线程的资源成本小,工作效率高
4、Linux下cpu所看到的所以进程都可以看成轻量级的进程
5、进程是承担分配系统资源的基本实体,进程具有独立性(但进程间通信打破了独立性)
6、线程是cpu或操作系统调度的基本单位,线程具有共享性
这里,我们只使用 pthread_create函数创建一个新的线程,体验一下线程技术暂不涉及回收线程等。
功能:创建一个新的线程 原型 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*star t_routine)(void*), void *arg); 参数 thread:返回线程ID attr:设置线程的属性,attr为NULL表示使用默认属性 start_routine:是个函数地址,线程启动后要执行的函数 arg:传给线程启动函数的参数 返回值:成功返回0;失败返回错误码 |
#include #include #include #include #include #include #include
char buf[100] = {0};
void *func(void *arg) { while (1) { memset(buf, 0, sizeof(buf)); printf("before read.\n"); read(0, buf, 5); printf("读出键盘的内容是:[%s].\n", buf); } }
int main(void) { int fd = -1; int ret = -1; pthread_t th = -1;
/* 创建线程 */ ret = pthread_create(&th, NULL, func, NULL); if(ret !=0) { perror("pthread_create error.\n"); return -1; } //因为主线程是while(1)死循环,所以可以在这里pthread_detach分离子线程 //主任务 fd = open("/dev/input/mouse0", O_RDONLY); if(fd < 0) { perror("open error.\n"); return -1; } while(1) { /* 读鼠标 */ memset(buf, 0, sizeof(buf)); ret = read(fd, buf, 50); if(ret > 0) {printf("鼠标读出的内容是:[%s].\n", buf); } } return 0; } |
编译时需要连接pthread线程库,即-lpthread
效果一致,但这里我们只使用了一个进程。
(1)一种轻量级进程
(2)线程是参与内核调度的最小单元,线程依附于进程。
(3)线程是进程内部的一个执行分支,线程量级很小。(所谓的内部就是在进程的地址空间内运行)
(4)一切进程至少都有一个线程。
PCB1相当于主线程,新线程PCB2、PCB3、PCB4相当于用vfork创建出来的,它们指向同一块地址空间,它们隶属于同一个进程,但是他们有着自己的线程ID
(1)像进程一样可被OS调度
(2)同一进程的多个线程之间很容易高效率通信
(3)在多核心CPU(对称多处理器架构SMP)架构下效率最大化
(4)一般来说,线程之间切换需要操作系统所做的工作比进程间切换需要的代价小。
(1)编写线程需要非常仔细的设计。
(2)对多线程的调试困难程度比单线程调试大得多。
功能:创建一个新的线程 原型 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*star t_routine)(void*), void *arg); 参数 thread:返回线程ID attr:设置线程的属性,attr为NULL表⽰示使⽤用默认属性 start_routine:是个函数地址,线程启动后要执⾏的函数 arg:传给线程启动函数的参数 返回值:成功返回0;失败返回错误码 |
int pthread_join(pthread_t thread,void **retval) thread:等待退出线程的线程号。 value_ptr:退出线程的返回值。 返回值:成功返回0;失败返回错误码 |
作用:
- 主线程等待新线程退出,否则就会导致进程的内存泄漏
- 回收新线程的退出结果
1:线程自己退出后释放自己资源
int pthread_detach(pthread_self())
2:线程组内其他线程对目标线程进行分离 int pthread_detach(pthread_t thread) |
#include #include #include #include #include void *thread_run( void * arg ) { printf("new thread dead\n"); pthread_detach(pthread_self()); return NULL; } int main( void ) { pthread_t tid; if ( pthread_create(&tid, NULL, thread_run, "thread1 run...") != 0 ) { printf("create thread"); } void *ret; sleep(1); if ( pthread_join(tid, NULL ) == 0 ) { printf("pthread wait success,ret:%d\n",(int )ret);
} else { printf("pthread wait failed\n");
} return 0; } |
功能:杀死一个执行中的线程 原型 int pthread_cancel(pthread_t thread); 参数 thread:线程ID 返回值:成功返回0;失败返回错误码 |
int pthread_setcancelstate(int state, int *oldstate)
设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE, 分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行; old_state:如果不为NULL则存入原来的Cancel状态以便恢复。 |
int pthread_setcanceltype(int type, int *oldtype)
设置本线程取消动作的执行时机 type由两种取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出); oldtype:如果不为NULL则存入运来的取消动作类型值。 |
功能:线程终止 原型 void pthread_exit(void *value_ptr); 参数 value_ptr:value_ptr 返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身) |
void pthread_cleanup_push(void( *fn)(void*)),(void* arg)) 函数执行压栈清理函数的操作, void pthread_cleanup_pop(int excute) 函数执行从栈中删除清理函数的操作。
pthread_cleanup_push() 函数,它的返回值类型为 void,有两个入口参数,第一个参数是清理函数函数指针,第二个参数是传给清理函数的 typeless pointer 。 pthread_cleanup_pop() 函数是来触发清理函数的,是按照相反的顺序来触发清理函数的。而如果它的入口参数 execute 为0值,则对应的清理函数并没有真正的执行。
|
函数1:syscall(int number,...)//获取内核线程id 函数2:pthread_self()//获取用户态线程id |