linux:线程&&多线程 初见
Posted mbf330
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux:线程&&多线程 初见相关的知识,希望对你有一定的参考价值。
文章目录
线程
1. Linux线程概念
线程是在进程中独立运行的子任务,可以理解为一个“轻量级进程”,一个进程内可以有多个线程,这些线程使用同一个PCB。
1.在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”。
2.一切进程至少都有一个执行线程。
3.线程在进程内部运行,本质是在进程地址空间内运行。
4.在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化。
5.透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。
2.创建线程
功能:创建一个新的线程
原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
参数
thread:返回线程ID。
attr:设置线程的属性,attr为NULL表示使用默认属性。
start_routine:是个函数地址,线程启动后要执行的函数。
arg:传给线程启动函数的参数。
返回值:成功返回0;失败返回错误码。
3.用一小段代码简单理解线程
如下代码创建了一个main线程,一个thread_run线程,运行之后,可以发现两个线程使用同一个PCB(pid相同)
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
//线程的tid代表线程的起始地址
void *thread_run(void *arg)
while(1)
printf("I am %s;pid:%d,my thread id:%p\\n",(char*)arg,getpid(),pthread_self());
sleep(1);
//thread_run执行流和main执行流使用同一个pid
int main()
pthread_t tid;
//创建线程,线程id放在tid里
pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
while(1)
printf("I am main thread id: %p,new thread id: %p, pid:%d\\n",pthread_self(),tid,getpid());
sleep(1);
return 0;
4.线程的优点&&缺点
优点
1.创建一个新线程的代价要比创建一个新进程小得多。
2.与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。
3.线程占用的资源要比进程少很多。
4.能充分利用多处理器的可并行数量。
5.在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
6.计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
7.I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
缺点
1.性能损失;一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
2.健壮性降低;编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
3.缺乏访问控制;进程是访问控制的基本粒度,在一个线程中调用某些系统调用函数会对整个进程造成影响。
4.编程难度提高;编写与调试一个多线程程序比单线程程序困难得多。
合理的使用多线程,能提高CPU密集型程序的执行效率,能提高IO密集型程序的用户体验
死锁
想要多个线程安全访问同一份临界资源,便要给线程加锁
1.死锁概念
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。
可以理解为,a,b两个线程想要访问并修改同一份临界资源x,但是这份资源一次只允许一个线程进行访问和修改,于是给a,b两个线程各规定一个被允许访问的条件ax,bx,在ax条件满足时,a线程持有锁访问并修改该临界资源,此时b线程便无法进入这份临界资源,a经过修改x使ax条件不满足后,a线程释放锁,之后b线程持有锁进入临界资源x。
可以引申到更多的线程之间。
2.死锁四个必要条件
1.互斥条件:一个资源每次只能被一个执行流使用。
2.请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺。
4.循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系。
3.关于 锁 的条件变量函数
1.初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
参数:
cond:要初始化的条件变量
attr:NULL
2.销毁
int pthread_cond_destroy(pthread_cond_t *cond)
3.等待条件的满足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量
4.唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
4.互斥量
为什么 pthread_cond_wait 需要互斥量?
条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。
生产者消费者模型
321原则:
三种关系:生产者VS生产者(互斥);生产者VS消费者(互斥&&同步);消费者VS消费者(互斥)
两个角色:生产者;消费者
一个场景:临界资源
1.为何要使用生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
2.生产者消费者模型优点
解耦;支持并发;支持忙闲不均。
3.queue模拟阻塞队列的生产消费模型
#ifndef __QUEUE_BLOCK_H__
#define __QUEUE_BLOCK_H__
#include<unistd.h>
#include<iostream>
#include<pthread.h>
#include<queue>
using namespace std;
class Task
public:
int x;
int y;
public:
Task(int _x,int _y):x(_x),y(_y)
Task()
int Run()
return x+y;
~Task()
;
class BlockQueue
private:
queue<Task> q;
size_t cap;
pthread_mutex_t lock;
pthread_cond_t c_cond;
pthread_cond_t p_cond;
public:
//判满
bool IsFull()
return q.size()>=cap;
//判空
bool IsEmpty()
return q.empty();
//解锁
void UnlockQueue()
pthread_mutex_unlock(&lock);
//唤醒消费者
void WakeUpConsumer()
cout<<"WakeUpConsumer"<<endl;
pthread_cond_signal(&c_cond);
//唤醒生产者
void WakeUpProductor()
cout<<"WakeUpProductor"<<endl;
pthread_cond_signal(&p_cond);
//pthread_cond_wait传入lock参数:
//在条件满足时,消费者or生产者持有锁进入临界区执行,当判断条件不满足时,调用对应的wait函数
//在消费者or生产者等待时,调用对应的Wait函数,自动释放lock
//消费者等待,必须解锁,让另一个角色持有锁,以保证线程之间友好,在
void ConsumerWait()
cout<<"ConsumerWait"<<endl;
pthread_cond_wait(&c_cond,&lock);
//生产者等待
void ProductWait()
cout<<"ProductWait"<<endl;
pthread_cond_wait(&p_cond,&lock);
public:
BlockQueue(int _cap):cap(_cap)
pthread_mutex_init(&lock,nullptr);
pthread_cond_init(&c_cond,nullptr);
pthread_cond_init(&p_cond,nullptr);
~BlockQueue()
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&p_cond);
pthread_cond_destroy(&c_cond);
//消费者消费,拿取or执行队列中的数据以及任务并执行
void Put(Task in)
UnlockQueue();
if(IsFull())
WakeUpConsumer();
ProductWait();
q.push(in);
UnlockQueue();
//生产者生产,向队列中塞数据或者任务
void Get(Task &out)
UnlockQueue();
if(IsEmpty())
WakeUpProductor();
ConsumerWait();
out=q.front();
q.pop();
UnlockQueue();
;
#endif
#include "block.h"
using namespace std;
void *consumer_run(void *arg)
//int num=(int)arg;
pthread_mutex_t lock;
BlockQueue *bq=(BlockQueue*)arg;
while(true)
pthread_mutex_lock(&lock);
//int data;
Task t;
bq->Get(t);
//t.Run();
pthread_mutex_unlock(&lock);
cout<<"consumer"<<t.x<<"+"<<t.y<<"="<<t.Run()<<endl;
sleep(1);
//pthread_mutex_unlock(&lock);
void *productor_run(void *arg)
//int num=(int)arg;
pthread_mutex_t lock;
sleep(1);
BlockQueue *bq=(BlockQueue*)arg;
//int count=0;
while(true)
int x=rand()%10+1;
int y=rand()%100+1;
pthread_mutex_lock(&lock);
Task t(x,y);
bq->Put(t);
pthread_mutex_unlock(&lock);
cout<<"productor "<<x<<"+"<<y<<"=?"<<endl;
//pthread_mutex_unlock(&lock);
int main()
BlockQueue *bq = new BlockQueue(5);
pthread_t con1,pro1;
pthread_create(&con1,nullptr,consumer_run,(void*)bq);
pthread_create(&pro1,nullptr,productor_run,(void*)bq);
//pthread_create(&con2,nullptr,consumer_run,(void*)bq);
//pthread_create(&pro2,nullptr,productor_run,(void*)bq);
pthread_join(con1,nullptr);
//pthread_join(con2,nullptr);
pthread_join(pro1,nullptr);
//pthread_join(pro2,nullptr);
return 0;
以上是关于linux:线程&&多线程 初见的主要内容,如果未能解决你的问题,请参考以下文章
Oracle12c(12.1)中性能优化&功能增强之通过参数THREADED_EXECTION使用多线程模型