以下是阿鲤对Linux下线程的总结,希望对大家有所帮助;若有误请慷慨指出。
1:线程概念
2:线程控制
2:线程安全
4:死锁
注:以下的代码实现均为centos7环境;
一:线程概念
1:线程的介绍:
在传统操作系统中对程序的描述方式分为pcb和tcb即进程和线程;而在Linux下其进程和线程均是通过pcb进行描述的;pcb:是一个文件描述信息,其使用虚拟地址空间对内存进行访问;在这里就有了进程和线程的区别。一个进程拥有一个虚拟地址空间,而多个线程拥有一个虚拟地址空间;可以这么理解,多个线程(一个线程组)构成一个进程;所以说Linux下没有真正的线程。Linux下的线程是轻量级进程。图解如下:
所以进程是系统资源分配的基本单位,而线程是cpu调度的基本单位
2:线程的独有与共享:
独有:栈(函数调用),寄存器(程序信息),信号屏蔽字(pcb中的阻塞信号),errno(错误信息),标识符(存储在共享区,在创建中使用tid返回首地址);
共享:虚拟地址空间(代码段,数据段),文件描述符表(可以减少文件打开次数),信号处理方式(信号是针对进程的),工作路径,用户ID,组ID
3:多进程与多线程多任务处理比较
多线程特点:
1:线程间通信很方便(包括进程间通信+全局变量等);
2:线程的创建与销毁成本低;
3:线程间的调度成本低;
4:指向粒度更加细腻
多进程特点:
1:具有独立性,因此更加的稳定,健壮;
共同特点:
1:并行压缩cpu处理/IO等待时间
所以对主功能程序安全稳定性要求高的最好使用多进程,剩下的使用多线程。
二:线程控制
注:Linux下线程控制的接口都是库函数(操作系统没有向用户提供一个轻量级进程接口,因此大佬们就对进程控制的接口进行了封装,从而封装出线程控制库函数)
1:线程创建
接口:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
*thread:用于获取线程ID,通过这个ID可以找到线程描述信息,进而访问pcb(线程的首地址,存储在虚拟地址的共享区);
attr:线程熟悉,通常NULL;
start_routine:线程入口函数,创建的线程就是为了运行这个函数,函数运行完毕,线程退出;
arg:通过线程入口函数,传给线程的参数(通信)
return:0-成功, 非0值-失败
eg:
#include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h> void *func(void *arg) { while(1) { printf("The main thread passed me a parameter:%d\n",*((int*)(arg))); sleep(1); } return NULL; } int main() { int tmp = 888; pthread_t tid; int ret = pthread_create(&tid, NULL, func, (void*)&tmp); if(ret != 0) { printf("thread create error: %d\n", ret); return -1; } while(1) { printf("I am main thread\n"); sleep(1); } return 0; }
讯享网

注意:因为是库函数,所以需要使用-l进行库的链接;
线程查看:

如上图使用ps -efL | head -n 1 && ps -efL | grep create是查看线程的命令(其中L选型为轻量级进程);其中LWP是线程ID,PID是进程ID;所以我们可以看出来进程ID是主线程ID。
2:线程终止
终止方式
普通线程入口函数中的return(mian函数中的return退出的是进程);
void pthread_exit(void *retval);退出一个线程,谁调用谁退出 retval-线程返回值;
int pthread_cancel(pthread_t thread);取消一个指定的线程;tid:指定的线程id;
注意:
线程退出也不会完全释放资源,需要被其他线程等待;
使用int pthread_cancel(pthread_t thread);取消自己是一个违规操作;
主线程退出,其他线程正常运行,这也不是主流做法
主线程退出,并不影响整个进程的运行,只有所有线程退出,进程才会退出。
3:线程等待
等待一个线程的退出,获取退出线程的返回值,回收这个线程所占用的资源
接口:
int pthread_join(pthread_t thread, void retval);
thread:等待线程的id
retval:用于获取线程退出的返回值
return:0-成功, 非0值-失败
eg:
讯享网 #include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h> void *func(void *arg) { sleep(3); char *buf = "BelongAl"; pthread_exit(buf); return NULL; } int main() { pthread_t tid; int tmp; int ret = pthread_create(&tid, NULL, func, (void*)&tmp); if(ret != 0) { printf("thread create error: %d\n", ret); return -1; } void *retval = NULL; pthread_join(tid, &retval); printf("%s\n",(char*)retval); return 0; }
注:不是所有的线程都能被等待,一个线程被创建,默认情况下有一个属性joinable;处于joinable属性的线程退出后,不会被自动释放,需要等待;
4:线程分离
将线程的属性从joinable设置为detach;处于detach属性的线程退出后会自动释放资源,不需要被等待。
接口:
int pthread_detach(pthread_t thread);
thread:指定线程id
return:0-成功, 非0值-失败

注:
等待一个被分离的线程,则pthread_detach会返回错误:这不是一个joinable线程(因为在获取返回值时将获取不到,detach属性的线程退出后已经自动的释放了资源)。
线程的分离可以在任意地方,可以在线程入口函数中让线程分离自己,也可以让创建线程在创建之后直接分离。
三:线程安全
1:概念:在多个执行流中对同一个临界资源进行操作访问,而不会造成数据二义;
2:方法
互斥:通过保证同一时间只有一个执行流可以对临界资源进行访问,来保证数据访问的安全性。
同步:通过一些条件判断来实现多个执行流对临界资源访问的合理性(有资源则访问,无资源则等待直到有资源被唤醒)。
3:互斥的实现:互斥锁
1:第一个线程访问时,判断可以访问,因此将状态置为不可访问(计数器置为0),然后去访问资源
2:其他线程访问的时候,发现不可访问,就陷入等待(将线程置为可中断休眠状态)
3:第一个线程访问临界资源完毕后,将状态置为可以访问(计数器置为1),唤醒等待的线程(将线程置为运行状态),被唤醒的进程开始竞争这个资源
计数器原子性操作原理:我们可以发现,每一个线程都可以对计数器进行修改,那么只有计数器修改操作是原子性的,才可以保证线程安全;那么计数器是怎样实现原子性呢?请看下图

互斥锁操作接口:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr):锁的初始化
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);销毁锁
注:pthread_mutex_t:互斥锁变量类型;attr:互斥锁属性
eg:
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<pthread.h> int tickets = 100;//表示有100张票 pthread_mutex_t mutex; void *thr_tout(void *arg) { while(1) { pthread_mutex_lock(&mutex);//阻塞加锁 if(tickets > 0) { printf("tout:%p - get a ticket:%d\n",pthread_self(), tickets); tickets--; pthread_mutex_unlock(&mutex);//解锁 } else { pthread_mutex_unlock(&mutex); pthread_exit(NULL);//线程退出 } } return NULL; } int main() { int i = 0, ret; pthread_t tid[4]; //互斥锁初始化 //pthread_mutex_init() 或 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER pthread_mutex_init(&mutex, NULL); for(; i < 4; i++) { ret = pthread_create(&tid[i], NULL, thr_tout, NULL); if(ret != 0) { printf("thread cerat error\n"); return -1; } } for(i = 0; i < 4; i++)//线程等待 { pthread_join(tid[i], NULL); } pthread_mutex_destroy(&mutex); return 0; }
4:同步的实现:条件变量
有资源的时候可以获取,没有资源的时候则需要让线程等待,等待被唤醒(其他线程产生一个资源的时候)
条件变量:向用户提供了两个接口,使一个线程等待接口和唤醒一个线程接口+等待队列;条件变量只是提供了等待与唤醒的功能,但是什么时候等待,什么时候唤醒,需要用户自己来做判断
操作接口:
pthread_cond_t :条件变量类型
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);//初始化
cond:条件变量
attr:条件变量属性
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//初始化
int pthread_cond_destroy(pthread_cond_t *cond);//销毁
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);//使当前执行流等待,加入等待队列
int pthread_cond_signal(pthread_cond_t *cond);//唤醒至少一个等待队列中的执行流
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有等待队列中的执行流
eg:吃面做面模型
讯享网 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<pthread.h> //定义两个条件变量是防止在唤醒时唤醒错误 pthread_cond_t gourment_cond;//定义一个美食家变量 pthread_cond_t chief_cond;//定义一个厨师变量 int have_delicacy = 0;//刚开始并没有美食 pthread_mutex_t mutex;//定义一个互斥锁 void *gourment(void *arg)//美食家函数 { while(1) { pthread_mutex_lock(&mutex);//加锁 while(have_delicacy == 0)/*如果没有美食则需要等待,但是在等待前需要先解锁//循环的原因为 pthread_cond_signal函数是唤醒至少一个,为了防止争抢混乱所采用的。例如在厨师做了美食之后,唤醒了一个或多个美食家若不采用循环,只有一个美食家会进入而其他美食家会陷入锁的阻塞,在这个美食家吃完之后会解锁然后再唤醒厨师,但是再唤醒厨师之前,那些阻塞再锁前的美食家可能会先行加锁但是因为不是循环等待,则会导致休眠之后解锁直接去吃美食,导致逻辑混乱 */ { pthread_cond_wait(&gourment_cond, &mutex);//等待函数包括:解锁-》休眠-》加锁 } printf("really delicious\n"); have_delicacy--; pthread_mutex_unlock(&mutex); //解锁 pthread_cond_signal(&chief_cond); //唤醒厨师 } return NULL; } void *chief(void *arg)//厨师长函数 { while(1) { pthread_mutex_lock(&mutex);//加锁 while(have_delicacy == 1)//如过做好了没有人吃则陷入等待 { pthread_cond_wait(&chief_cond, &mutex); } printf("I made a bowl of Buddha jumping over the wall\n"); have_delicacy++; //做出没事之后唤醒等待的美食家 pthread_mutex_unlock(&mutex);//解锁 pthread_cond_signal(&gourment_cond);//唤醒美食家 } return NULL; } int main() { pthread_t gourment_tid, chief_tid; int ret; pthread_cond_init(&chief_cond, NULL);//初始化厨师条件变量 pthread_cond_init(&gourment_cond, NULL);//初始化美食家条件变量 pthread_mutex_init(&mutex,NULL);//初始化互斥锁 int i = 0; for(i = 0; i < 4; i++) { ret = pthread_create(&gourment_tid, NULL, gourment, NULL);//创建美食家线程 if(ret != 0) { printf("pthread create error\n"); return -1; } } for(i = 0; i < 4; i++) { ret = pthread_create(&chief_tid, NULL, chief, NULL);//创建厨师线程 if(ret != 0) { printf("pthread create error\n"); return -1; } } pthread_join(gourment_tid, NULL); pthread_join(chief_tid, NULL); pthread_cond_destroy(&chief_cond);//销毁厨师条件变量 pthread_cond_destroy(&gourment_cond);//销毁美食家条件变量 pthread_mutex_destroy(&mutex);//销毁互斥锁 return 0; }
4:同步的实现:信号量(POSIX标准)(也可实现互斥)
本质:计数器+等待队列+向外提供的使执行流阻塞/唤醒的功能接口。通过对资源进行计数,统计当前资源数量,通过自身的计数,就可以进行条件判断,是否能够进行操作,若不能获取资源,则阻塞当前执行流。在程序初始化阶段,根据实际资源数量初始化信号量计数器,在每次获取资源之前先获取信号量(先去判断计数器是否小于0,若大于0,则计数-1,直接返回,获取数据,否则阻塞当前执行流;其他执行流生产一个资源后,先判断计数器是否<0,若小于0,则唤醒一个执行流,然后计数器+1)
接口:
sem_t sem : 信号量类型
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem:信号量ID
pshared:这个参数决定了当前信号量适用于进程间还是线程间;0-线程间/非0-进程间
value:实际资源数量,用于初始化信号量计数器初值
int sem_wait(sem_t *sem);//阻塞等待
int sem_trywait(sem_t *sem);//尝试等待
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);//等待一段时间
int sem_post(sem_t *sem);//唤醒操作
int sem_destroy(sem_t *sem);//销毁
eg:生产者与消费者模型
#include<iostream> #include<semaphore.h> #include<vector> #define MAX_QUEUE 5 class ringQueue { std::vector<int> m_array; int m_capacity; int m_pos_write;//写指针 int m_pos_read;//读指针 sem_t m_consumer_sem;//数据资源计数器,消费者计数器 sem_t m_product_sem;//空闲空间计数器,生产者计数器 sem_t m_sem_lock;//锁 public: ringQueue(int capacity = MAX_QUEUE): m_capacity(capacity), m_pos_read(0), m_pos_write(0), m_array(MAX_QUEUE) { sem_init(&m_consumer_sem, 0, 0);//数据资源初始化 sem_init(&m_product_sem, 0, MAX_QUEUE);//空闲时间初始化 sem_init(&m_sem_lock, 0, 1);//锁的初始化 } ~ringQueue() { sem_destroy(&m_product_sem); sem_destroy(&m_consumer_sem); sem_destroy(&m_sem_lock); } bool push(int &data) { //没有空间空间则直接阻塞,并且空闲空间计数-1; sem_wait(&m_product_sem); sem_wait(&m_sem_lock);//加锁,保护入队操作 m_array[m_pos_write++] = data; if(m_pos_write == m_capacity) { m_pos_write = 0; } sem_post(&m_sem_lock);//解锁, sem_post(&m_consumer_sem);//资源计数器+1,唤醒消费者 return true; } bool pop(int *data) { //通过资源计数器判断是否否能获取资源,资源计数器-1 sem_wait(&m_consumer_sem); sem_wait(&m_sem_lock);//加锁 *data = m_array[m_pos_read++]; if(m_pos_read == m_capacity) { m_pos_read = 0; } sem_post(&m_sem_lock);//解锁 sem_post(&m_product_sem);//空闲计数器+1,唤醒生产者 return true; } }; void *product(void* arg) { ringQueue *rq = (ringQueue*)arg; int i = 0; while(1) { rq->push(i); std::cout << "productor: " << pthread_self() << "put data: " << i++ << std::endl; } return NULL; } void *consumer(void *arg) { int data; ringQueue *rq = (ringQueue*)arg; while(1) { rq->pop(&data); std::cout << "consumer: " << pthread_self() << "get data: " << data << std::endl; } return NULL; } #define MAX_THR 4 int main() { int ret, i; pthread_t ptid[MAX_THR], ctid[MAX_THR]; ringQueue rq; for(i = 0; i < MAX_THR; i++) { ret = pthread_create(&ptid[i], NULL, product, (void*)&rq); if(ret != 0) { std::cout << "thread create error" << std::endl; return -1; } } for(i = 0; i < MAX_THR; i++) { ret = pthread_create(&ctid[i], NULL, consumer, (void*)&rq); if(ret != 0) { std::cout << "thread create error" << std::endl; return -1; } } for(i = 0; i < MAX_THR; i++) { pthread_join(ptid[i], NULL); pthread_join(ctid[i], NULL); } return 0; }
四:死锁
1:概念:多个执行流在对多个锁资源进行争抢操作,但是因为推进不当,而导致互相等待,流程无法继续推进的情况。
2:死锁产生的四个必要条件
1,互斥条件:一个锁只有一个人能加,我加了锁,别人就不能加了
2,不可剥夺条件:我加的锁,别人不能替我释放
3,请求与保持条件:我加了A锁,然后去请求B锁,但是请求不到B锁,我也不释放A锁
4,环路等待条件:eg:有甲乙两人,AB两锁;甲持有A锁,乙持有B锁,;甲请求B锁,乙请求A锁;在满足条件三的情况下,形成了环路等待
3:死锁的预防
即破坏产生死锁的四个必要条件
1:锁资源按序一次性分配
2:加锁的时候可以使用非阻塞加锁,若无法加锁,则将手中的其他锁释放掉
4:死锁的避免
1:银行家算法:定义三张表:现在有多少钱(现在都有那些锁);现在那些人已经借了钱(当前那些执行流已经获取了锁);当前还有那些人需要借多少钱(当前那些执行流想要那些锁);
若给一个执行流分配指定的锁有可能会造成环路等待(非安全状态),则不予分配,并且回溯释放当前执行流已有的资源。
2:死锁监测算法

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/39978.html