一、线程概念
在一个程序里的一个执行路线就叫做线程。线程是“一个进程内部的控制序列”。
linux下没有真正的线程,linux下的线程是用进程pcb来模拟的,linux下的线程也叫轻量级进程。既然pcb成为了线程,则进程变为线程组(进程id=线程id)。
linux下的pcb是线程,所以线程是cpu调度的基本单位。
进程是操作系统资源分配的基本单位:运行一个程序时,资源就会完全分配,并且这些资源是分配给线程组中的所有线程(因为他们共用这些资源),资源是以进程分配的,进程中的所有线程共用。
一切进程至少都有一个线程。
线程共享进程数据,但也有自己的数据。
线程是cpu的调度基本单位,进程是资源分配的基本单位。
线程优点:linux下的线程共用进程的虚拟地址空间(共享代码段和数据段)。
1.线程的创建/销毁成本更低;
2.线程的调度切换成本更低;
3.线程占用的资源比进程少很多;
4.线程间的通信更加方便;
5.线程能充分利用多处理器的可并行数量;
6.执行粒度更加细致;
7.在等待慢速I/O操作结束的同时,程序可执行其他的计算任务;
8.计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现;
9.I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
缺点:
1.缺乏访问控制。有些系统调用和程序异常是针对整个进程产生影响;
2.多个线程对公共资源(临界资源)进行操作会造成数据混乱;
3.性能损失。一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失。这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
4.健壮性降低。在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
5. 程序难度提高。
即便有很多缺点,但是线程依然被用于每个项目。
多线程/多进程:
1.io密集型程序----分摊等待
2.cpu密集型程序----分摊计算
线程共享:虚拟地址空间,文件描述符表,信号处理方式,用户id,组id,当前工作目录。
线程独有:(相对独有--因为独有的数据还是在虚拟地址空间中)栈区,上下线程id,一组寄存器,errno,信号屏蔽字,调度优先级
二、线程控制
操作系统并没有提供实现线程控制的接口。所以就为线程控制封装了一套线程库,使用这套接口创建的线程称之为用户态的线程,但是它在操作系统内部对应了一个轻量级进程。
1.线程创建
pthread_create
函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
thread:用于获取线程id(用户态的线程id)
attr:设置线程属性,通常置NULL
void *(*start_routine) (void *):线程的入口函数
void *arg:作为线程入口函数的参数
返回值:0----成功/非0----失败,返回的是错误编号
线程id:tid pid tgid == 主线程的pid
pthread_t pthread_self(void); 获取主线程
会通过参数返回一个用户态的线程id---是线程地址空间在进程虚拟地址空间中的首地址。这个用户态
2.线程终止
不能再main中return,不能调用exit,因为这两个都是退出进城的,进程退出了,所有的线程都得退出。
pthread_exit:谁调用谁退出 退出自己
pthread_cancel:取消其它线程 退出别人
3.线程等待:获取其他普通线程的退出返回值,避免产生僵尸线程
int pthread_join(pthreat_t thread, void ** retval);
thread:指定等待的线程id
retval:获取线程的退出返回值
只有线程处于joinable状态(线程默认属性),这个线程才能被等待。
4.线程分离:线程退出后,直接释放资源,无法获取返回值。
int pthread_detach(pthread_t thread);
thread:指定要分离的线程id
设置线程的属性为分离属性(detach状态),退出后直接释放资源。只有在我们不关心线程的退出返回值的时候才会设置。因为一旦设置就无法pthread_join。
分离指定的线程,被分离的线程退出时自动被回收资源。因为资源立即被回收,所以不会保存返回值,也就无法被等待。
三、线程同步与互斥
1.线程安全:多个线程因为临界资源的争抢写入操作会导致程序逻辑的混乱/数据二义性,因此就引入了线程安全的概念。但是线程的使用本身就是因为线程间的通讯方便以及成本低而广为使用,这样的话就无法避免大量临界资源的争抢操作,这时候就必须要考虑如何保证线程安全。
保证线程的安全,更多指的是保证数据的安全访问(互斥/同步)。
2.互斥:互斥锁——保证数据同一时间的唯一访问
互斥锁的应用
volatile ticket = 100;
void * yellow_cow(void *arg)
{
int id = (int )arg;
while(1)
{
if(ticket>0){
printf("cow %d get ticket:%d\n",id,ticket);
usleep(100);
ticket--;
}
else{
pthread_exit(NULL);
}
}
return NULL:
}
int main()
{
int max = 4, i;
pthread_t tid[4];
for(i=0; i<max; i++)
{
int ret = pthread_create(&tid[i],NULL,yellow_cow, NULL)
if(ret!=0)
{
printf("create thread error!!!"\n);
return -1;
}
}
for(i = 0; i<max;i++)
{
}
return 0;
}
Using namespace std;sa
#include<stdio.h>
#include<unistd.h>
#include<stdilb.h>
#include<queue>
#include<pthread.h>
#define NUM 10
class BlockQueue
{
private:
std::queque<int> _q;
pthread_mutex_t mutex;
pthread_cond_t empty;
pthread_cond_t full;
void QueueBlock()
{
pthread_mutex_lock(&mutex);
}
viod QueueUnLock()
{
pthread_mutex_unlock(&mutex);
}
void ProductWait()
{
pthread_cond_wait(&full, &mutex);
}
void ProductNotice()
{
pthread_cond_signal(&full);
}
void ConsumerWait()
{
pthread_cond_wait(&empty, &mutex);
}
void ConsumerNotice()
{
pthread_cond_signal(&empty);
}
bool QueueIsEmpty()
{
return (_q.size()==0);
}
bool QueueuIsFull()
{
return (_q.size()==_cap);
}
public:
BlockQueue(int cap = NUM):_q(cap), _cap(cap)
{
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&empty,NULL);
pthread_cond_init(&full,NULL);
}
~BlockQueue()
{
pthread_mutex_destory(&mutex);
pthread_cond_destory(&empty);
pthread_cond_destory(&full);
}
bool PushData(int data)
{
Queue();
if(QueueIsFull()){
ProductWait();
}
_q.push(data);
ConsumerNotice();
return true;
}
bool PopData(int &data)
{
QueueLock();
if(QueueIsEmpty()){
ConsumerWait();
}
data = _q.front(); //获取对手数据
_q.pop();
ProductNotice();
QueueUnLock();
return true;
}
};
void *productor(void *arg)
{
int i = 0;
BlockQueue *q = (BlockQueue*)arg;
while(1){
q->PushData(i++);
sleep(1);
}
return NULL;
}
void *consumer(void *arg)
{
int i = 0;
BlockQueue *q = (BlockQueue*)arg;
while(1){
int data;
q->PushData(data);
printf(“get data:%d\n”, data);
usleep(100000);
}
return NULL;
}
int main()
{
pthread_t tid1, tid2;
int ret;
BlockQueue q;
ret = pthread_creat(&tid1, NULL, predictor, (void *)$q);
}
线程池:线程+队列
为什么使用线程池:避免大量线程的创建/销毁时间成本;避免峰值压力造成程序错误。
设计模式:单例模式
线程安全的单例模式:加锁
某些类,只应该具有一个
对象只能初始化一次
Static T *data
饿汉/懒汉(更希望写懒汉模式)
If(data == NULL)
Data = new
懒汉模式中需要注意的是线程安全
Lock
If(data==NULL)
Data = new
Unlock
让性能更加好一点