自学嵌入式第N+1天(Linux篇——线程)
上一篇讲了进程我们知道了进程的创建过程就是复制他的父进程以此来提高他的并发。那么进程和线程有啥区别呢进程是重量级线程资源分配的基本(最小)单位更侧重成为一个资源分配的单位线程轻量级进程线程是CPU的基本调度单位线程侧重于成为一个执行体 执行单位进程组成 pcb [text|data|bss|堆|栈]线程组成 线程ID 程序计数器 一组相关寄存器 栈空间 由于其他几个所占内存都非常小栈空间比他们大很多所以线程就是就约等于栈空间大小线程是CPU使用的基本单元它由线程ID、程序计数器、寄存器集合和栈组成。它与属于同一进程的其他线程共享代码段、数据段和其他操作系统资源如打开文件和信号。一个传统重量级的进程只有单个控制线程如果进程有多个控制线程那么它能同时做多个任务。代码实现1.线程创建int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);功能创建一个线程参数: thread为线程IDattr为线程属性NULL为默认属性也就是可结合属性还有可分离属性后面再讲start_routine 线程可执行函数 arg 给线程执行函数的参数 。void * start_thread (void *arg){//完成线程执行功能的}参数:arg是由pthread_create最后一个参数传递过来的返回值:void * 返回一个指针返回值:成功 0失败 返回error number不是之前的errno这里需要将自己手动赋值即errno返回值注意:线程共用的是进程空间资源共享如果操作同一个资源相互之间会影响线程之间关系是对等不存在进程间父子关系查看线程的命令:ps -eLf2.执行函数就是创建线程的第三个参数3.退出1void pthread_exit(void *retval);功能:结束调用线程参数:retval表示线程要带出的值也就是状态值注意:如果想要获得 该线程的 退出状态值则需要另外的线程 通过pthread_join来获取pthread_exit 传递的是退出状态值 对应的一块空间的地址pthread_join 接受的是这个地址注意:如果是在main线程中调用 pthread_exit()此时只是将主线程的执行结束了但不是整个进程结束了。要等所有子线程都执行结束进程的空间才会销毁。2int pthread_join(pthread_t thread, void **retval);功能:等待线程结束并回收线程资源参数:thread是要等待的线程的tidretval是获取退出状态值的参数返回值:成功 返回0失败返回错误码。3int pthread_cancel(pthread_t thread);功能向同一进程内的目标线程发送取消请求。只是发请求不会立刻杀死线程目标线程什么时候退出由线程自身的取消属性决定默认等到取消点sleep/read/pthread_mutex_lock/printf 等阻塞函数才响应参数thread目标线程的pid号返回值成功返回0失败返回错误码。下面用一个例子来演示上面几个函数创建两个线程一个线程复制文件前一半一个线程复制后一半。#include stdio.h #include unistd.h #include pthread.h #include fcntl.h #include sys/stat.h typedef struct { int fd_s; int fd_d; int size; }msg_t;//方便给执行函数传参 void * do_cp1(void *arg) { msg_t *p arg; lseek(p-fd_s,0,SEEK_SET); lseek(p-fd_d,0,SEEK_SET); char buf[p-size/2]; int ret read(p-fd_s,buf,sizeof(buf)); write(p-fd_d,buf,ret); return NULL; } //这里需要注意读写指针的位置在文件编程的时候说过 void * do_cp2(void *arg) { msg_t *p arg; lseek(p-fd_s,p-size/2,SEEK_SET); lseek(p-fd_d,p-size/2,SEEK_SET); char buf[p-size -p-size/2]; int ret read(p-fd_s,buf,sizeof(buf)); write(p-fd_d,buf,ret); return NULL; } void cleanup(void *arg) { msg_t *p arg; close(p-fd_s); close(p-fd_d); printf(---close---file\n); } int main(int argc, const char *argv[]) { int fd_s open(argv[1],O_RDONLY); int fd_d open(argv[2],O_WRONLY|O_TRUNC|O_CREAT,0666); if (fd_s 0 || fd_d 0) { perror(open fail); return -1; } struct stat st; if (stat(argv[1],st) 0) { perror(stat fail); return -1; } msg_t m; m.size st.st_size; m.fd_s fd_s; m.fd_d fd_d; pthread_t tid[2]; int ret pthread_create(tid[0],NULL,do_cp1,m); if (ret ! 0) { perror(pthread_create fail); return -1; } ret pthread_create(tid[1],NULL,do_cp2,m); if (ret ! 0) { perror(pthread_create fail); return -1; } pthread_cleanup_push(cleanup,m); pthread_join(tid[0],NULL); pthread_join(tid[1],NULL); pthread_exit(NULL); pthread_cleanup_pop(0); return 0; }4退出清理函数a.void pthread_cleanup_push(void (*routine)(void *) void *arg);功能注册一个线程清理函数参数routine是线程清理函数的入口arg是清理函数的参数。b.void pthread_cleanup_pop(int execute);功能调用清理函数参数execute为非0 执行清理函数为0不执行清理只有在push 之后、pop 之前线程执行pthread_exit() 被pthread_cancel()取消且走到取消点才会执行cleanup函数5设置线程属性设置分离属性目的是让线程消亡自动回收空间。a.int pthread_attr_init(pthread_attr_t *attr);功能初始化一个attr的变量参数attr需要变量来接受初始值返回成功返回0错误返回非0 b.int pthread_attr_destroy(pthread_attr_t *attr);功能销毁attr变量。参数attr属性变量返回成功返回错误返回非0 c.int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);功能把一个线程设置成相应的属性参数attr属性变量有init函数初始化他detachstate有2个可选值PTHREAD_CREATE_DETACHED 使用该属性创建的线程为分离态。PTHREAD_CREATE_JOINABLE 使用该属性创建的线程为可汇合态。返回值成功返回0失败返回非0d.int pthread_detach(pthread_t thread);功能设置分离属性参数线程id号填自己的id返回值成功返回0失败返回非0这里是两个设置分离属性的方法具体区别如下对比项pthread_attr_setdetachstatepthread_detach调用时机pthread_create 之前设置 attr 属性pthread_create 之后调用作用对象还未创建的线程模板属性已经存在、正在运行的线程使用流程①attr_init → ②setdetachstate → ③pthread_create(attr) → ④attr_destroy①pthread_create → ②传入 tid 调用 pthread_detach (tid)能不能设置 JOINABLE两种都能设置分离 / 可汇合只能设置成分离态不能改回可汇合典型场景批量创建一批线程统一全部设为分离态线程运行中途动态设置分离线程内部pthread_detach(pthread_self())限制只对本次用这个 attr 创建的线程生效一旦 detach 之后不能再 pthread_join 这个线程6子线程的回收注意如果预估子线程可以有限范围内结束则正常用pthread_join等待回收。如果预估子线程可能休眠或者阻塞则等待一定时间后强制回收。如果子线程已知必须长时间运行则不再回收其资源。就设置为分离属性。a.pid_t wait (int *wstatus)头文件#includesys/types.h #includesys/wait.h功能等待子进程状态发生改变子进程结束子进程被暂停子进程被恢复可以阻塞等待任意子进程退出并回收该进程的资源和获取退出状态值。用于父进程回收子进程。参数传一个int变量地址内核会把子进程退出信息填到这个变量里。WIFEXITED(status)子进程是否正常退出WEXITSTATUS(status)拿到子进程 main return /exit () 返回值WIFSIGNALED(status):判断是否是信号结束WTERMSTG(status):是哪个信号结束返回值成功返回回收子进程的pid号失败返回-1当不关心子进程状态是可以用waitNULLb. pid_t waitpid(pid_t pid, int *wstatus, int options);功能和wait一样但是可以选择是否阻塞等待。参数wstatus和wait一样pid 值含义pid 0只等待PID 等于该值的那一个子进程pid 0等待同进程组的任意子进程pid -1等待任意一个子进程等价于wait()pid -1等待指定进程组内任意子进程取绝对值为进程组 IDoptions为0时是阻塞等待为WNOHANG是非阻塞等待返回值成功时返回回收子进程的pid号失败时返回-1非阻塞时返回0表示没有任何子进程状态改变。waitpid(-1,status,0)等价于waitstatus。