ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux应用软件编程04-线程

Linux应用软件编程04-线程 一、线程介绍1.1 线程基本概念线程是一个轻量级的进程一个任务创建、调度、消亡的过程用来实现多任务并发。线程是操作系统任务调度的最小单位。线程由某个进程创建。1.2 线程的创建线程由所在进程创建创建时进程为其分配独立的栈区空间默认8M。同一个进程中的线程共用所在进程的堆区、数据区全局变量、静态变量、字符串常量、文本区。1.3 线程的调度与进程调度保持一致宏观并行微观串行1.4 线程消亡线程结束必须回收线程空间否则会产生僵尸线程1.5 进程和线程的区别进程线程正在执行的程序轻量级的进程操作系统资源分配的最小单位操作系统任务调度的最小单位资源空间消耗大0-4G虚拟内存空间资源空间消耗小栈区独立其他区域共享进程效率低创建、任务切换线程效率高创建、任务切换进程安全性高进程空间独立线程安全性低进程间通信较复杂需要用到IPC机制线程间通信简单全局变量在相同资源平台下多进程的并发量少于多线程1.6 多进程和多线程优缺点1运行效率多线程 多进程多线程在同一进程空间内部任务调度效率更高。2安全多进程 多线程进程空间是独立的一个进程异常结束不会影响其余进程。线程位于进程空间内部一个线程异常结束会导致进程结束其余线程也随之结束。3通信实现多线程 多进程线程位于进程空间内部共享进程的文本段、数据段全局变量、静态变量、字符串常量、堆区所以可以通过共享空间实现通信。进程空间是独立的没有共享空间无法通信。4通信编程复杂程度多进程 多线程多线程使用全局变量通信会引入资源竞争问题需要加锁来解决。多进程空间独立的没有共享空间需要使用第三方机制来完成进程间通信。二、线程相关接口函数2.1 pthread_createint pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 功能在进程中创建一个线程 参数 thread 存放线程ID空间的首地址 attr 线程的属性栈空间大小、 优先级、 分离/加入属性默认属性传NULL start_routine 函数指针指向线程要执行的函数线程启动后要执行的任务的函数地址线程任务函数 arg 传递给线程任务函数的参数 返回值 成功0 失败返回错误码注意编译和链接时要加-lpthread 表示链接线程库。线程执行时线程所在的进程不能先退出。2.2 pthread_exitvoid pthread_exit(void *retval); 功能结束线程任务 参数 retval 线程结束时的状态 返回值 缺省2.3 pthread_joinint pthread_join(pthread_t thread, void **retval); 功能回收线程空间 参数 thread:回收线程的TID retval:存放线程结束状态空间的首地址 返回值 成功返回0 失败返回错误码具有阻塞功能pthread_join() 函数会等待由 thread 参数指定的线程完成其执行过程。如果该线程已经结束执行那么 pthread_join() 将立即返回。由 thread 指定的线程必须处于非分离状态。如果 retval 不为 NULL则 pthread_join() 会将目标线程的退出状态复制到由 retval 所指向的位置。如果目标线程被取消pthread_cancel则会在由 retval 所指向的位置放置 PTHREAD_CANCELED (-1) 标志。2.4 pthread_selfpthread_t pthread_self(void);功能返回调用该函数的线程自身的线程IDTID练习1创建3个线程#include head.h void *thread1fun(void *arg) { printf(线程1(TID:%#lx)开始执行\n, pthread_self()); return NULL; } void *thread2fun(void *arg) { printf(线程2(TID:%#lx)开始执行\n, pthread_self()); return NULL; } void *thread3fun(void *arg) { printf(线程3(TID:%#lx)开始执行\n, pthread_self()); return NULL; } int main(void) { pthread_t tid[3]; int i 0; void *(*p[3])(void *) {thread1fun, thread2fun, thread3fun}; for (i 0; i 3; i) { pthread_create(tid[i], NULL, p[i], NULL); } for (i 0; i 3; i) { pthread_join(tid[i], NULL); } #if 0 pthread_t tid1tid2tid3; pthread_create(tid1, NULL, thread1fun, NULL); pthread_create(tid2, NULL, thread2fun, NULL); pthread_create(tid3, NULL, thread3fun, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_join(tid3, NULL); #endif return 0; }#include head.h #define DEBUG 0 void *thread(void *arg) { #if DEBUG int *pval arg; printf(线程%d(TID:%#lx)开始执行\n, *pval, pthread_self()); #else int val (int)arg; printf(线程%d(TID:%#lx)开始执行\n, val1, pthread_self()); #endif return NULL; } int main(void) { int i 0; pthread_t tid[3] {0}; #if DEBUG int val[3] {1, 2, 3}; #endif for (i 0; i 3; i) { #if DEBUG pthread_create(tid[i], NULL, thread, val[i]); #else pthread_create(tid[i], NULL, thread, (void *)i); #endif } for (i 0; i 3; i) { pthread_join(tid[i], NULL); } return 0; }练习2生产者与消费者编程思想创建4线程任务线程1开始时打印 采集线程(TID:)开始执行然后间隔1s打印 正在采集线程2开始时打印 存储线程(TID:)开始执行然后间隔2s打印 正在存储线程3开始时打印 显示线程(TID:)开始执行然后间隔5s打印 正在显示线程4开始时打印 日志线程(TID:)开始执行然后间隔10s打印 正在日志记录主进程负责从终端接收字符 如果接收到的是a或者A 采集线程停止/开始打印b或者B 存储线程停止/开始打印c或者C 显示线程停止/开始打印d或者D 日志线程停止/开始打印#include head.h typedef struct pthread_info { pthread_t tid; //线程ID号 char threadname[32]; //线程名 int IntervalSec; //间隔秒数 int IsWork; //是否打印标志位 }PthreadInfo_t; void *threadfun(void *arg) { PthreadInfo_t *pthreadinfo arg; printf(%s (TID:%#lx)开始执行\n, pthreadinfo-threadname, pthreadinfo-tid); while (1) { if (pthreadinfo-IsWork) { printf(%s正在运行\n, pthreadinfo-threadname); sleep(pthreadinfo-IntervalSec); } } return NULL; } int main(void) { char ch 0; PthreadInfo_t thread[4] { { .threadname 采集线程, .IntervalSec 1, .IsWork 0xffffffff, }, { .threadname 存储线程, .IntervalSec 2, .IsWork 0xffffffff, }, { .threadname 显示线程, .IntervalSec 5, .IsWork 0xffffffff, }, { .threadname 日志线程, .IntervalSec 10, .IsWork 0xffffffff, }, }; int i 0; for (i 0; i 4; i) { pthread_create(thread[i].tid, NULL, threadfun, thread[i]); } while (1) { scanf( %c, ch); switch (ch) { case a: case A:thread[0].IsWork ~thread[0].IsWork;break; case b: case B:thread[1].IsWork ~thread[1].IsWork;break; case c: case C:thread[2].IsWork ~thread[2].IsWork;break; case d: case D:thread[3].IsWork ~thread[3].IsWork;break; } } for (i 0; i 4; i) { pthread_join(thread[i].tid, NULL); } return 0; }三、线程的分离属性① 分离属性具有分离属性的线程在运行结束后由系统自动回收其资源如线程控制块、栈空间等无需其他线程调用pthread_join进行等待。这类线程一旦终止其资源立即被释放外界无法感知其结束时刻。② 非分离属性具有非分离属性的线程在其终止后资源不会自动释放必须由其他线程调用pthread_join来等待其结束并回收资源。③默认属性线程默认具有非分离属性。④ 设置线程分离属性int pthread_detach(pthread_t thread); 功能设置线程的分离属性 参数 thread要设置的线程的线程ID 返回值 成功0 失败0 int pthread_attr_init(pthread_attr_t *attr); 功能将 attr 指向的线程属性进行初始化 int pthread_attr_destroy(pthread_attr_t *attr); 功能将 attr 指向的线程属性销毁 int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); 功能设置线程属性 分离状态PTHREAD_CREATE_DETACHED 非分离状态可连接状态PTHREAD_CREATE_JOINABLE#include head.h void *thread(void *arg) { printf(线程(TID:%#lx)开始执行\n, pthread_self()); return NULL; } int main(void) { pthread_t tid[3]; int i 0; pthread_attr_t attr; pthread_attr_init(attr); pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED); for (i 0; i 3; i) { pthread_create(tid[i], attr, thread, NULL); } pthread_attr_destroy(attr); while (1) { } return 0; }四、线程间通信①线程间通信方式线程使用全局变量可以实现通信。多线程同时使用全局变量会引入资源竞争需要通过互斥锁解决资源竞争问题。② 概念原子操作不能被CPU任务调度打断的一次最小的操作称为原子操作。临界代码/临界区加锁解锁中间的代码这段代码用户不会被同时执行。4.1 线程间互斥机制① 多线程访问临界资源时存在资源竞争问题。②临界资源多个线程可以同时操作的资源比如全局变量共享内存等。③ 如何解决资源竞争问题线程的互斥机制让多个线程在访问临界资源时具有排他性访问的特性④互斥机制互斥锁、信号量、读写锁写独占读共享、自旋锁⑤互斥锁实现互斥机制创建互斥锁pthread_mutex_t初始化锁pthread_mutex_init加锁pthread_mutex_lock/pthread_mutex_trylock解锁pthread_mutex_unlock销毁锁pthread_mutex_destroyint pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 功能初始化互斥锁 参数 mutex 互斥锁空间首地址 attr 互斥锁的属性 默认属性NULL 返回值 成功0 失败非0 int pthread_mutex_lock(pthread_mutex_t *mutex); 功能以阻塞方式等待加锁 int pthread_mutex_trylock(pthread_mutex_t *mutex); 功能以非阻塞方式加锁如果该锁已被使用则返回 int pthread_mutex_unlock(pthread_mutex_t *mutex); 功能解锁/释放锁 int pthread_mutex_destroy(pthread_mutex_t *mutex); 功能销毁锁#include head.h pthread_mutex_t lock; int num 0; void *thread1(void *arg) { while (1) { pthread_mutex_lock(lock); num 100; pthread_mutex_unlock(lock); } return NULL; } void *thread2(void *arg) { while (1) { pthread_mutex_lock(lock); num 200; printf(num %d\n, num); pthread_mutex_unlock(lock); } return NULL; } int main(void) { pthread_t tid1; pthread_t tid2; pthread_mutex_init(lock, NULL); pthread_create(tid1, NULL, thread1, NULL); pthread_create(tid2, NULL, thread2, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_mutex_destroy(lock); return 0; }⑥死锁1死锁指的是在多线程环境中每个执行流线程都有未释放的资源且互相请求对方未释放资源从而导致陷入永久等待状态的情况。由于多个锁的加锁解锁导致线程无法继续执行的状态称为死锁2产生死锁的四个充分必要条件四条全中 死锁破掉任意一条 解除1互斥条件一个资源同一时刻只能被一个任务占用一个执行流获取锁后其它执行流不能再获取该锁。2请求与保持条件一个任务已经持有了至少一个资源但它又提出了新的资源请求而该新资源正被其他任务占用。此时该请求进程会进入阻塞等待状态但是对自己已经持有的资源又保持不放。3不可剥夺条件资源只能由持有者主动释放在它使用完毕主动释放之前系统/其他任务不能强行抢过来A执行流拿着锁其它执行流不能释放。4循环等待条件若干进程之间形成一种头尾相接的循环等待资源关系多个执行流拿着对方想要的锁并且各执行流还去请求对方的锁。3避免产生死锁使用pthread_mutex_trylock替代pthread_mutex_lock锁一定要成对出现使线程的加解锁顺序一致破坏环路等待条件使用非阻塞锁一旦线程发现请求的锁被使用就去释放自己拥有的资源4.2 线程间同步机制①线程间同步让多个线程任务执行某部分任务的时候具有先后顺序。②同步方式信号量③ 可以将信号量看成一个资源数当该资源数0的时候可以申请到信号量当资源时为0时申请信号量阻塞等待。申请完信号量时该信号量对应的资源数自动-1。释放对应的信号量时该信号量对应的资源数自动1。④信号量是一种资源在程序运行过程中代码、变量、 CPU、内存都可以看成是一种资源。资源一定是有限的资源只能被初始化、销毁、申请和释放。⑤函数接口创建信号量sem_t初始化信号量sem_init申请信号量sem_wait();sem_trywait();释放信号量sem_post();销毁信号量sem_destroy();int sem_init(sem_t *sem, int pshared, unsigned int value); 功能初始化信号量 参数 sem:信号量的地址 pshared: 0 同一进程的线程间共享 非0 进程间共享 value:初始化的资源数值 返回值 成功返回0 失败返回-1 int sem_destroy(sem_t *sem); 功能销毁信号量 参数 sem:信号量的首地址 返回值 成功返回0 失败返回-1 int sem_wait(sem_t *sem); 功能 申请信号量 如果资源数0则让资源数-1 如果资源数 0,阻塞等待直到资源 0 才能申请到资源继续向下执行 参数 sem:信号量空间首地址 返回值 成功返回0 失败返回-1 int sem_post(sem_t *sem); 功能释放资源 让资源数1 参数 sem:信号量空间首地址 返回值 成功返回0 失败返回-1练习实现3个线程任务线程1循环打印A线程2循环打印B线程3循环打印C但是要求打印出来的顺序总是ABC#include head.h sem_t semA; sem_t semB; sem_t semC; void *threadA(void *arg) { while (1) { sem_wait(semA); printf(A); sem_post(semB); } return NULL; } void *threadB(void *arg) { while (1) { sem_wait(semB); printf(B); sem_post(semC); } return NULL; } void *threadC(void *arg) { while (1) { sem_wait(semC); printf(C); sem_post(semA); } return NULL; } int main(void) { pthread_t tidA; pthread_t tidB; pthread_t tidC; sem_init(semA, 0, 1); sem_init(semB, 0, 0); sem_init(semC, 0, 0); pthread_create(tidA, NULL, threadA, NULL); pthread_create(tidB, NULL, threadB, NULL); pthread_create(tidC, NULL, threadC, NULL); pthread_join(tidA, NULL); pthread_join(tidB, NULL); pthread_join(tidC, NULL); sem_destroy(semA); sem_destroy(semB); sem_destroy(semC); return 0; }
返回列表