线程在Linux内核中没有独立的线程数据结构线程的本质是共享资源的轻量级进程(Lightwegiht Process,简称LWP)内核视角每个线程对应一个任务控制块(task_struct),内核通过task_struct管理调度单元资源共享多个LWP(线程)通过共享同一进程地址空间(mm_struct)、文件描述符(files_struct)、信号处理表(signal_struct)“等核心资源实现线程共享进程资源的特性与传统进程的区别普通进程的task_struct对应独立的mm_struct(独立地址空间)而线程的task_struct共享同一mm_struc本质上是同一进程地址空间下的多个调度单元”Linux线程与POSIX标准的兼容标准遵循Linux原生POSIX线程库(libpthread)完全遵循POSIX线程标准提供与其他UNIX-like系统一致的线程接口确保多平台代码可移植性核心接口与库依赖线程创建(pthread_creat)、线程等待(pthread_join)等核心操作均由libpthread.so库提供编码时需包含头文件#include (声明库中函数接口)编译时需显式链接线程库gcc 源码.c -o 可执行文件 -lpthread否则会报未定义引用错误内核交互方式Linux下操作进程需通过libpthread库的POSIX接口而非直接调用内核系统调用用户态代码通过库封装与内核交互线程依附于进程存在线程既不属于系统调用是属于第三方库所以在编译的时候需要链接相关的库gcc xxx.c -lpthread -o “可执行文件名”相关函数1、创建线程所需头文件#includepthread原型intpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*arg);功能在调用进程中创建一个新的线程 参数thread用于存储新线程的唯一标识符(线程ID)attr用于指定新线程的属性分离式还是结合式 若是NULL则使用系统默认属性(结合式)start_routine指向线程的入口函数 arg用于给线程入口函数传递参数地址传递如果不传参则填NULL 返回值成功返回0新线程ID会写入thread指向的变量中 失败返回非0错误码 示例pthread_ttdpthread_create(td,NULL,函数名,向函数传入的参数(参数名不传参就填NULL))2、获取线程的线程号所需头文件#includepthread.h原型pthread_tpthread_self(void);功能获取调用线程的线程ID 参数无 返回值成功总是成功返回调用线程的线程ID3、线程终止所需头文件#includepthread原型voidpthread_exit(void*retual);功能结束当前线程 参数retual用来给回收资源的线程传递线程的退出状态值 若不想返回退出状态值可以传NULL返回值无 注意1、主线程中如果从main函数return返回或者是调用了exit()函数退出当前进程则整个进程终止此时所 有的其他线程也将终止2、子线程如果调用return或pthread_exit则只有该线程结束但调用exit或_exit则整个进程结束3、主线程如果调用了pthread_exit函数则仅仅是主线程消亡进程不会结束其他线程也不会结束直 到所有的线程都结束的时候进程才结束。4、retual必须是在线程结束后不会消亡的值否则主线程无法回收4、回收线程资源1、阻塞方式回收线程资源 所需头文件#includepthread原型intpthread_join(pthread_tthread,void**retval)功能阻塞方式等待指定线程退出并释放线程未释放的资源(线程私有栈等资源)参数thread存储要回收资源的线程ID retval用来接收线程退出的状态值不关心传NULL即可 返回值成功返回0失败返回非0错误码 注意1、任何一个线程都可以等待其他的线程退出2、如果等待的线程没有结束则阻塞调用者3、一个线程结束的时候部分资源没有释放[私有栈,TID...],pthread_join可以释放线程未释 放的资源pthread_join一般是主线程来调用用于等待子线程退出因为是等待所以阻塞一般主线程会依次join所有的子线程资源回收管理模式线程创建后默认处于结合态若未显示设置为分离态其终止后资源不会立即释放必须由其他线程(通常是主线程)通过pthread_join()主动回收分离态的线程终止后由系统自动回收资源无需其他线程调用pthread_join()对比维度结合态(默认)分离态资源回收方式必须通过其他线程调用pthread_join()回收线程终止后系统自动回收资源pthread_join()作用有效调用后阻塞直到目标线程终止并回收其资源无效调用会返回错误无法等待分离态线程未回收的风险若主线线程未调用pthread_join()线程终止后会成为僵尸线程占用task_struct等资源无风险终止后资源立即释放不会产生僵尸线程状态设置时机/方式创建时默认(无需额外操作) 也可以通过pthread_detach函数从结合态转为分离态在pthread_create()的attr参数中设置PTHREAD_CREATE_SETACHED;适用场景需要获取线程退出的场景无需获取线程退出状态 希望自动释放资源的场景2、非阻塞方式回收线程资源 所需头文件#includepthread原型intpthread_detach(pthread_tthread);功能标记一个线程为分离式线程线程结束之后资源会自动回收(非阻塞的资源回收)参数thread线程ID 返回值成功返回0失败返回非0错误码5、取消线程所需头文件#includepthread原型intpthread_cancel(pthread_tthread);功能请求取消指定的线程 目标线程对取消请求的处理取决于取消状态和取消类型 参数thread要取消的线程的id号 返回值成功返回0失败返回非0错误码pthread_exit是结束当前线程pthread_cancel取消另一个线程执行pthread_setcancelstate函数所需头文件#include pthread 原型int pthread_setcancelstate(int state, int *oldstate); 功能设置调用线程的可取消状态 PTHREAD_CANCEL_ENABLE可被取消(默认) PTHREAD_CANCEL_DISABLE不可被取消 参数state新的可取消状态 oldstate旧的可取消状态 返回值成功返回0失败返回非0错误码pthread_setcanceltype函数所需头文件#include pthread 原型int pthread_setcanceltype(int type, int *oldtype); 功能设置调用线程的可取消类型 PTHREAD_CANCEL_DEFERRED延时取消(默认)直到下一次调用能作为取消点的函数时才被取消 PTHREAD_CANCEL_ASYNCHRONOUS可被立即取消 参数type要设置的新的可取消类型值 oldtype存储旧的可取消类型值 返回值成功返回0失败返回非0错误码分离式线程创建概念 在任何一个时间点上线程是可结合的joinable或者是分离的detached。一个可结合的线程能够被其他线程收回其资源和杀死在被其他线程回收之前它的存储器资源如栈是不释放的。一个分离的线程是不能被其他线程回收或杀死的它的存储器资源在它终止时由系统自动释放方法1简单-----推荐使用在创建线程pthread_create(pthread,NULL,function,NULL)进行错误判断后调用pthread_detach(thread)将该线程设置为分离式线程方法2进阶--------要注意创建线程设置为分离式线程可能在返回值错误判断之前线程就已经开始运行了1、定义pthread_attr_tattr变量2、初始化变量 所需头文件#includepthread原型intpthread_attr_init(pthread_addr_t*attr);功能将一个线程属性对象初始化为默认值 参数attr需要初始化的线程对象 返回值成功返回0失败返回非0错误码3、设置属性将属性设置为分离式还是结合式 所需头文件#includepthread原型intpthread_attr_setdetachstate(pthread_attr_t*attr,intdetachstate);功能设置线程属性对象的分离状态 参数attr设置分离状态的线程属性对象 detachstate设置线程的属性通过宏传入 PTHREAD_CREATE_DETACHED 分离式线程 PTHREAD_CREATE_JOINABLE 结合式线程 返回值成功返回0失败返回非0错误码4、创建线程并将attr传入pthread_create(pthread,attr,function,NULL)5、在创建成功后摧毁attr 所需头文件#includepthread原型intpthread_attr_destory(pthread_attr_t*attr);功能销毁一个已初始化的线程属性对象 参数attr指定需要被销毁的线程属性对象 返回值成功返回0失败返回非0错误码 注意 销毁attr对用其初始化且已经运行的线程没有影响 销毁过后的attr也可以重新用pthread_attr_init初始化**线程的同步和互斥****互斥锁**互斥锁(Mutex,即Mu tual Exclusion Lock)是并发编程中用于保护共享资源的同步工具本质是一把独占钥匙 为什么需要互斥锁1.当多个线程同时读写共享资源时会出现竞态条件导致数据错误2.互斥锁能强制线程排队访问资源避免竞争 核心作用保证同一时刻只有一个线程能持有锁并访问共享资源实现互斥访问**1.线程的互斥**概念一个线程访问资源的时候不允许其他线程访问 互斥锁 c1.定义互斥锁定义在全局pthread_mutex_tmutex;2.初始化互斥锁在创建线程之前初始化 所需头文件#includepthread.h原型intpthread_mutex_init(pthread_mutex_t*restrict mutex,constpthread_mutexattr_t*restrict attr);功能动态初始化互斥锁 参数mutex指定要进行初始化的互斥锁变量 attr设置互斥锁的属性若传NULL使用系统默认属性pthread_mutex_tPTHREAD_MUTEX_INITIALIZER;——静态初始化互斥锁 返回值成功返回0失败返回非0错误码3.上锁在预期要一起执行的代码段落之前上锁 所需头文件#includepthread.h原型intpthread_mutex_lock(pthread_mutex_t*mutex);功能用于获取互斥锁 当互斥锁已被其他线程获取时调用该函数的线程会被阻塞直到互斥锁可用并成功获取 参数mutex互斥锁 返回值成功返回0失败返回非0错误码 所需头文件#includepthread.h原型intpthread_mutex_trylock(pthread_mutex_t*mutex);功能用于获取互斥锁 当互斥锁已被其他线程获取时调用该函数的线程不会被阻塞而是立即返回错误 参数mutex互斥锁 返回值成功返回0失败返回非0错误码4.解锁在预期要一起执行的代码段落之后解锁 所需头文件#includepthread.h原型intpthread_mutex_unlock(pthread_mutex_t*mutex);功能解锁 一般使用时只有加锁的线程可以解锁 参数mutex互斥锁 返回值成功返回0失败返回非0错误码5.摧毁锁在线程退出后程序结束前摧毁 所需头文件#includepthread.h原型intpthread_mutex_destroy(pthread_mutex_t*mutex);功能销毁互斥锁 参数mutex互斥锁 返回值成功返回0失败返回非0错误码2.线程的同步概念进程间/线程间 相互配合按照一定的顺序完成通过信号量同步无名信号量无名信号量是一种用于线程同步或进程同步的工具通过维护一个计数值来控制对共享资源的访问可限制同时访问资源的线程或进程数量为什么需要无名信号量1.互斥锁只能保证同一时刻一个线程访问资源而有些场景需要允许多个线程同时访问2.无名信号量的计数值可灵活设置能实现多线程有限并发访问或线程间的计数同步补充互斥锁在多资源并发场景的不足核心作用通过计数值控制共享资源的并发访问数量支持多线程在一定限制下同时访问资源或实现线程间基于计数的同步协作步骤1.声明变量sem_t2.初始化 所需头文件#includesemaphore.h原型intsem_init(sem_t*sem,intpshared,unsignedintvalue);功能初始化无名信号量 参数sem:要初始化信号量 pshared:确定用于线程还是进程 pshared0信号量用于进程内的线程间同步(多个线程共享进程内存信号量仅在进程内有效)pshared!0信号量用于进程间同步(需将信号量放在共享内存中被多个进程映射后共同访问)value:信号量的初始计数表示可用资源的初始数量 value1初始时有1个资源可用可用于实现互斥(一次一个线程/进程获取)value0初始时没有资源可用调用sem_wait的线程/进程会阻塞直到sem_post增加计数 返回值成功返回0失败返回-1重置错误码3.执行操作生产和消费 原型intsem_wait(sem_t*sem);//消费功能信号量的值减1如果信号量的当前值大于0减1后函数直接返回(表示获取资源成功)如果信号量的当前值等于0sem_wait会阻塞直到有其他线程/进程调用sem_post函数使得信号量值大于0后 再执行减1并返回 参数sem无名信号量 返回值成功返回0失败返回-1重置错误码(不会改变信号量的值)原型intsem_post(sem_t*sem);//生产功能释放信号量(将信号量的值1)参数sem用于指定对哪个信号量进行释放操作 返回值成功返回0失败返回-1重置错误码(不会改变信号量的值)4.销毁信号量释放相关资源 原型intsem_destroy(sem_t*sem);功能销毁无名信号量释放该信号量相关的资源 参数sem指定对哪个信号量进行销毁操作 返回值成功返回0失败返回-1重置错误码(不会改变信号量的值)//两个线程交替打印AB #include stdio.h #include stdlib.h #include unistd.h #include pthread.h #include semaphore.h sem_t read_sem, write_sem; //定义两个信号量 void *read_thread(void *arg) { for (int i 0; i 10; i) { sem_wait(read_sem); //从read_sem中申请一个资源如果没有资源就等待。P操作 printf(A\n); sem_post(write_sem); //把资源释放到write_sem中 V操作 } } void *write_thread(void *arg) { for (int i 0; i 10; i) { sem_wait(write_sem); //从write_sem中申请一个资源如果没有资源就等待。P操作 printf(B\n); sem_post(read_sem); //把资源释放到read_sem中 V操作 } } int main(int argc, char const *argv[]) { sem_init(read_sem, 0, 1); //初始化信号量线程使用资源数为1 sem_init(write_sem, 0, 0); pthread_t tid[2]; int ret pthread_create(tid[0], NULL, read_thread, NULL); if (ret ! 0) { printf(create thread err! errcode: %d\n, ret); exit(1); } ret pthread_create(tid[1], NULL, write_thread, NULL); if (ret ! 0) { printf(create thread err! errcode: %d\n, ret); exit(1); } pthread_join(tid[0], NULL); pthread_join(tid[1], NULL); sem_destroy(read_sem); sem_destroy(write_sem); return 0; }通过条件变量同步条件变量条件变量是并发编程中用于实现线程间等待-通知机制的同步工具可让线程在某个条件不满足时挂起待条件满足后被唤醒为什么需要条件变量1.仅用互斥锁能保证共享资源访问的互斥性但无法解决线程需等待特定条件才能继续执行的场景(比如生产者-消费者模型中消费者需等待生产者产出数据才能消费)2.条件变量可结合互斥锁让线程在条件不满足时主动释放锁并等待条件满足时被唤醒以重新获取锁继续执行灵活应对依赖条件的同步需求核心作用实现线程间的条件等待与通知使线程能在特定条件下协作避免线程忙等情况消耗CPU资源提升并发效率步骤1.声明变量intflag;pthread_cond_tcond;2.初始化 所需头文件:#includepthread.h原型intpthread_cond_init(prhread_cond_t*restrict cond,constpthread_condattr_t*restrict attr);功能动态初始化条件变量 参数cond用于指定要初始化的条件变量 attr设置条件变量的属性若传NULL使用系统默认属性pthread_cond_tcondPTHREAD_COND_INITIALIZER;——静态初始化条件变量 返回值成功返回0失败返回非0错误码3.等待条件变量 原型intpthread_cond_wait(prhread_cond_t*restrict cond,prhread_mutex_t*restrict mutex);功能线程阻塞在条件变量cond上等待被唤醒 同时该函数会自动释放传入的互斥锁mutex当线程被唤醒时又会自动重新获取该互斥锁 保证了线程在等待条件过程中互斥锁的正确管道避免死锁等问题 参数cond用于指定线程等待的条件变量 mutex与条件变量配合使用保证线程在等待条件时互斥 返回值成功返回0失败返回非0错误码4.唤醒条件变量 原型intprhread_cond_broadcast(prhread_cond_t*cond);//唤醒所有的条件变量功能唤醒所有等待在指定条件变量(cond)上的线程 参数cond指定要操作的条件变量 返回值成功返回0失败返回非0错误码1原型intprhread_cond_signal(prhread_cond_t*cond);//唤醒特定的条件变量功能唤醒至少一个等待在指定条件变量(cond)上的线程 参数cond指定要操作的条件变量 返回值成功返回0失败返回非0错误码15.销毁条件变量 原型intprhread_cond_destroy(prhread_cond_t*cond);功能销毁条件变量相关的资源 参数cond要销毁的条件变量 返回值成功返回0失败返回非0错误码线程的互斥与同步应用场景工具应用场景特点优势局限性互斥锁单一共享资源同一时刻只允许一个线程访问主要用于防止数据竞争和不一致性问题实现简单易于理解和使用 能有效解决单一资源的互斥访问问题保障数据的一致性和完整性如果线程长时间持有锁可能导致其他线程长时间等待降低系统并发性能 若使用不当容易产生死锁问题比如多个线程循环等待对方持有的锁条件变量线程需要在某个条件满足时才能继续执行 配合互斥锁实现线程间的等待-通知机制避免线程的忙等待有效节省CPU资源提高程序的运行效率 可以实现更灵活的线程同步逻辑满足复杂的条件等待和协作需求必须与互斥锁配合使用条件的判断和通知的时机需要准确把握否则可能导致线程无法正确唤醒或错过唤醒影响程序的正确性无名信号量限制对共享资源的并发访问数量 可用于线程或进程的同步和计数控制可以灵活控制并发访问的资源数量 适用于需要对资源进行定量管理的场景 不仅能实现互斥还能进行计数同步功能比互斥锁更丰富信号量的计数管理需要准确设置和维护容易出现资源过度使用或浪费的情况