Linux应用层开发 --- 线程 一. 线程是什么1线程是一个轻量级的进程用来实现多任务并发2线程是操作系统任务调度的最小单位进程是操作系统资源分配的最小单位二. 线程的创建线程由所在进程创建线程一定是属于某个进程的。线程创建时所在进程为其分配独立的栈区空间默认8M线程共用所在进程的堆区、数据区、文本区、内核区域三. 进程与线程的区别进程线程概念操作系统资源分配的最小单位CPU任务调度的最小单位效率进程需要被分配0-4G虚拟内存空间线程创建比进程创建快只需分配栈区空间线程间任务切换快内存内存消耗大相同资源环境下并发量比线程少只需要独立的栈区空间其他空间和进程共享安全性各进程空间独立安全性高线程共用内存空间没有进程安全性高通信进程不能直接通信需要使用IPC机制通信线程间可以直接通信全局变量、全局队列主线程也叫初始线程、主控线程程序运行起来之后操作系统创建进程进程自带第一个线程就是主线程。次线程又叫子线程、工作线程主线程调用 pthread_create() 手动创建出来的线程全部叫次线程。四. 线程的调度宏观并行、微观串行宏观并行一段时间内看起来多个线程同时在运行。 若干个子线程 主线程肉眼观察打印信息好像一起执行。微观串行单核 CPU 同一时刻CPU 核心只能执行一条线程代码。操作系统快速来回切换各个线程时间片轮转。在单核 CPU上同一瞬间只能跑 1 个线程轮流抢占 CPU一条跑一会儿切换下一条微观串行1 秒内若干个线程都得到了执行机会看起来同时工作宏观并行多核 CPU 补充 如果电脑是多核真正做到微观并行多个线程可以在不同核心上同时运行。这时 “微观串行” 就不再成立。调度器、时间片Linux 内核里面有线程调度器给每一条线程分配一小段执行时间时间片毫秒级别。线程拿到时间片 → 占用 CPU 运行时间片用完 → 线程被换下保存现场切换别的线程 切换速度极快人眼无法分辨。五. 线程的消亡1. 线程退出线程执行结束return线程所在进程结束pthread_exit退出线程#include pthread.hvoid pthread_exit(void *retval);2. 回收线程资源空间1线程的分离属性定义不需要被其他线程回收的线程称为具有分离属性的线程将来执行结束由操作系统回收将非分离属性的线程设置为分离属性的线程pthread_detach(tid);-----可设置tid对应的线程为分离属性的线程场景其他线程没有机会回收该线程时可以将该线程设置为分离属性2线程的非分离属性定义可以被其他线程结束和回收的线程称为具有非分离属性的线程。因此具有非分离属性的线程结束后必须要被其他线程回收默认属性非分离属性3回收方式​pthread_join()​​​​​​#include pthread.hint pthread_join(pthread_t thread, void **retval);功能阻塞回收线程资源空间和状态参数thread要回收的线程的线程IDretval保存线程退出状态的变量的地址NULL表示不接收状态返回值成功0失败0示例#includestdio.h #includepthread.h #includeunistd.h void *th_task(void *arg) { int cnt 10; while(1) { if(0 cnt--) { pthread_exit(NULL); //return NULL; //exit(0); } printf(I am thread: tid %ld\n,pthread_self()); sleep(1); } return NULL; } int main(void) { pthread_t tid; int ret pthread_create(tid, NULL, th_task, NULL); if(ret ! 0) { printf(pthread_create error\n); return -1; } pthread_join(tid,NULL); while(1) { printf(I am main thread: tid %ld\n,pthread_self()); sleep(1); } return 0; }六. 线程相关函数接口创建线程pthread_create()#include pthread.hint pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);功能创建并启动一个线程参数thread保存线程ID的变量地址attr线程属性的变量地址NULL按照默认属性创建start_routine函数指针指向线程执行的任务函数的指针线程任务函数arg传递给线程任务函数的参数返回值成功0失败错误码0注意使用线程相关接口时编译和链接需要链接线程库-lthread/-lpthread创建线程的主进程不能先于线程结束示例#includestdio.h #includepthread.h #includeunistd.h void *th_task(void *arg) { while(1) { printf(I am thread: tid %ld\n,pthread_self()); sleep(1); } return NULL; } int main(void) { pthread_t tid; int ret pthread_create(tid, NULL, th_task, NULL); if(ret ! 0) { printf(pthread_create error\n); return -1; } while(1) { printf(I am main thread: tid %ld\n,pthread_self()); sleep(1); } return 0; }七、线程的同步和互斥线程间通信全局变量、队列堆上申请、pthread_create传参、条件变量1. 线程间同步机制线程间同步让多个线程在执行某部分任务时具有一定的先后顺序信号量也可以认为是一种特殊的锁机制资源数实现线程间同步1创建信号量对象sem_t2初始化信号量#include semaphore.hint sem_init(sem_t *sem, int pshared, unsigned int value);功能初始化一个信号量参数sem信号量对象地址pshared0线程间共享使用0进程间共享使用value信号量初始值返回值成功0失败03申请信号量P操作int sem_wait(sem_t *sem);4释放信号量V操作int sem_post(sem_t *sem);5销毁信号量int sem_destroy(sem_t *sem);示例char buff[1024] {0}; sem_t sem_w; sem_t sew_r; void *read_data(void *arg) { while(1) { sem_wait(sem_w); //-1 fgets(buff, sizeof(buff), stdin); sem_post(sem_r); //1 } } void *write_data(void *arg) { while(1) { sem_wait(sem_r); //-1 fgets(buff, sizeof(buff), stdin); sem_post(sem_w); //1 } } int main(void) { pthread_t tid[2]; sem_init(sem_w, 0, 1); sem_init(sem_r, 0, 0); pthread_create(tid[], NULL, read_data, NULL); pthread_create(tid[], NULL, write_data, NULL); pthread_join(tid[1], NULL); pthread_join(tid[0], NULL); sem_destroy(sem_w); sem_destroy(sem_r); return 0; }2. 线程间互斥机制1多线程访问临界资源时存在资源竞争问题临界资源多个线程可以同时访问的内存区域如全局变量、全局队列临界区访问临界资源的这段代码称为临界区2线程的互斥机制让多个线程访问临界资源时具有排他性访问的特性3实现互斥机制互斥锁1创建一个互斥锁对象pthread_mutex_t2初始化互斥锁pthread_mutex_init();int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;功能初始化一个锁对象参数mutex要初始化的锁对象的地址attr锁属性的变量地址NULL按照默认属性创建返回值成功0失败03加锁保护临界区代码int pthread_mutex_lock(pthread_mutex_t *mutex);4解锁int pthread_mutex_unlock(pthread_mutex_t *mutex);5销毁互斥锁int pthread_mutex_destroy(pthread_mutex_t *mutex);示例#include stdio.h #include pthread.h int num_g 0; pthread_mutex_t mutex; void *task1(void *arg) { for (int i 0; i 100000; i) { pthread_mutex_lock(mutex); num_g num_g 1; printf(num_g %d\n, num_g); pthread_mutex_unlock(mutex); } return NULL; } void *task2(void *arg) { for (int i 0; i 100000; i) { pthread_mutex_lock(mutex); num_g num_g 1; printf(num_g %d\n, num_g); pthread_mutex_unlock(mutex); } return NULL; } int main(void) { pthread_t tid[2]; int ret 0; pthread_mutex_init(mutex, NULL); ret pthread_create(tid[0], NULL, task1, NULL); if (ret ! 0) { printf(pthread_create error\n); return -1; } ret pthread_create(tid[1], NULL, task2, NULL); if (ret ! 0) { printf(pthread_create error\n); return -1; } pthread_join(tid[0], NULL); pthread_join(tid[1], NULL); pthread_mutex_destroy(mutex); return 0; }八、死锁1. 定义死锁指的是在多线程环境中每个执行流线程都有未释放的资源且互相请求对方未释放资源从而导致陷入永久等待状态的情况。2. 现象现象1忘记释放锁现象2重复加锁现象3多线程多锁抢占锁资源不当 如线程A获取了1锁线程B获取了2锁同时线程A还想获取2锁线程B还想获取1锁3. 产生死锁的四个必要条件四条全中 死锁破掉任意一条 解除1 互斥条件一个资源同一时刻只能被一个任务占用一个执行流获取锁后其它执行流不能再获取该锁2 请求与保持条件已经拿着一部分资源不释放同时又去申请新的资源执行流本身使用着一把锁并不释放还在请求别的锁3 不剥夺条件:资源只能由持有者主动释放系统/其他任务不能强行抢过来A执行流拿着锁其它执行流不能释放。4 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系多个执行流拿着对方想要的锁并且各执行流还去请求对方的锁。4. 解决方法1锁一定要成对出现2使线程的加解锁顺序一致3破坏环路等待条件 使用非阻塞锁一旦线程发现请求的锁被使用就去释放自己拥有的资源int pthread_mutex_trylock(pthread_mutex_t *mutex);int sem_trywait(sem_t *sem);int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);