多线程

本文详细介绍了线程的基础概念,包括线程与进程的区别、线程的优点与缺点等,并深入探讨了线程控制、同步与互斥机制,以及条件变量的使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、线程概念

    在一个程序里的一个执行路线就叫做线程。线程是“一个进程内部的控制序列”。

    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;

}
pthread_mutex_lock;阻塞加锁
 
pthread_mutex_trylock;非阻塞加锁
 
pthread_mutex_timedlock限时阻塞加锁
 
pthread_mutex_init初始化互斥锁
 
pthread_mutex_t
 
pthread_mutex_unlock
 
pthread_mutex_destroy销毁互斥锁
 
在任何有可能退出的地方都必须在退出前解锁。
 
死锁:一个程序长时间获取不到锁,因此一直处于卡死状态
 
死锁产生的必要条件:
 
(1)互斥条件:只有一个人能获取锁
 
(2)不可剥夺条件:我的锁别人不能释放
 
(3)环路等待:我拿着我的去请求你的,你拿着你的来请求我的
 
(4)请求与保持条件:拿到第一个锁,去请求第二个锁的时候,拿不到但是还不释放第一个
 
预防死锁:
 
(1)破坏死锁的四个必要条件
 
(2)加锁顺序一致
 
(3)避免锁未释放的场景
 
(4)资源一次性分配
 
避免死锁
 
(1)死锁检测算法
 
(2)银行家算法
 
3.同步:条件变量——让数据的访问具有时序的可控性
 
四、条件变量——保证对临界资源访问的时序可控性
 
满足操作条件,才可以操作,不满足则等待,而条件要满足就需要其他线程修改条件,并且通知一下等待的线程。
 
互斥锁没有时序:因为没有条件判断,没有通知
 
pyhread_cond_t . cond 
 
int pthread_cond_init 初始化条件变量
 
pthread_cond_destroy销毁
 
pthread_cond_wite等待
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
    cond: 条件变量
    mutex: 互斥量
这个函数在挂起之前对互斥锁进行解锁操作,这时候解锁操作和挂起操作必须是一个原子操作,否则不安全。
 
pthread_cond_signal通知等待在条件变量上的线程/进程
 
生产者与消费者模型:
    功能:支持忙闲不均,解藕,支持并发
    一个场所:数据的存放位置
    两类角色:生产者、消费者
    三种关系:如何保证数据的安全访问(生产者与生产者的互斥关系、消费者与消费者的互斥关系、生产者与消费者的同步+互斥关系)
    c++实现一个线程访问安全队列BlockQueue
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

让性能更加好一点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值