Linux多线程

Linux线程

  • 01.Linux线程
  • -

1.什么是线程呢?线程是执行的最小单位,但不是资源分配的最小单位。举个例子,当你正在玩LOL的时候,又想听音乐,那么此时必须要有一个执行流来执行音乐的播放,此时多线程派上了用场。
2.线程是一个程序中独立执行的序列,是可调度的实体。
3.一个进程某一时刻只能做一件事情,有了多线程,在程序设计时就可以同一时刻做不止一件事情。

  • 02.进程和线程

1.一个进程至少有一个线程
2.同一个进程中的多个线程,共享同一地址空间,这就意味着同一进程的多个线程有很多资源是共享的,比如代码段,数据段。所以共享的:

a. 文件描述符
b. 对信号的处理方式
c. 工作目录
d. 用户ID和组ID

不同线程相互独立的:

a. 线程对用的"PID"
b. 线程的私有栈
c. 信号屏蔽字
d. 调度的优先级

  • 03.线程标识

1.为了对线程进程管理,那么内核必须将线程进行描述,然后才能管理
2.就像每个进程ID一样,每个线程也要有自己的ID来进行区分,进程ID在整个系统都是唯一的,但是线程不同,线程ID只要是在其所属进程上下文中是唯一的即可。

  • 04.线程的创建

1.线程创建函数
这里写图片描述
2.线程在程序中可以pthread_self来获取自己的线程ID
这里写图片描述

  • 05.线程的终止

1.如果一个进程调用了exit,_exit,return函数那么整个进程会终止。同样的线程也是类似的。
2.单个线程的退出方式。

a. pthread_exit( )

这里写图片描述

b. return


c.pthread_cancel

这里写图片描述
实例:

void* ThreadEntry_1(void* arg)
{
    (void)arg;

    std::cout<<"线程退出 1 !"<<std::endl;
    return ((void*)1);
}
void* ThreadEntry_2(void* arg)
{
    (void)arg;

    std::cout<<"线程退出 2 !"<<std::endl;
    return ((void*)2);
}

int main()
{

    pthread_t tid1;
    pthread_t tid2;
    void *rret;   
    pthread_create(&tid1,NULL,ThreadEntry_1,NULL);
    pthread_create(&tid2,NULL,ThreadEntry_2,NULL);
    pthread_join(tid1,&rret);
    std::cout<<"线程1的退出码 :"<<(long)rret<<std::endl;
    pthread_join(tid2,&rret);
    std::cout<<"线程2的退出码 :"<<(long)rret<<std::endl;

    return 0;
}
//程序执行结果
[skin@localhost Text]$ ./a.out 
线程退出 2 !
线程退出 1 !
线程1的退出码 :1
线程2的退出码 :2
  • 06.线程等待
  • -

1.为什么要线程等待呢?(想想进程什么要等待呢?)

a. 因为不等待线程退出,那么其现成的地址空间还在占用着,没有释放
b. 创建新的不会用这个地址空间,这样就造成地址空间的浪费,内存泄漏。

2.线程等待函数
这里写图片描述
实例:看上一个实例代码,原谅我的偷懒。

  • 07.线程分离
  • -

1.为什么要线程分离?

a. 想想在前几篇中linux进程中的僵尸进程和进程等待的关系。对于子进程的退出信息如果父进程不进行进程等待,那么子进程变成了僵尸进程。所以父进程就要进行进程等待,但是又引入了另一个问题,父进程就必须阻塞的等待了,没有办法去做其他事情了!。
b.对于上述问题,就可以看看信号那篇博客,子进程在退出时会发送一个SIGCHLD信号,父进程对其默认处理动作是忽略,所以我们可以对父进程SIGCHLD信号处理动作进行修改,在信号处理函数中等待子进程,这样父进程就可以去做自己的事情了,等子进程退出发送SIGCHLD信号,然后清理子进程。
c.线程分离就是要达到上述问题,如果不等待线程结束,那么线程也变成了"僵尸线程",造成内存泄漏问题。所以出现了线程分离,将这个线程进行和主线程分离,那么当这个线程结束的时候,会自己主动释其所占用的空间,这样就很好的完美的解决了"僵尸线程"的问题。而且我们会发现,这个线程分离实际要比上述信号驱动机制更好一点,因为主线程根本不用再做任何关于这个线程。
d.线程分离在我做的小项目中,典型的实例就是服务器对多客户端处理线程 。每来一个客户就开一个线程去处理客户需求,然后进行线程分离。

  • 08.线程的同步互斥

1.为什么要现成互斥呢?下面将一个经典的案例

a. 再说互斥时,必须说一个概念:临界资源,什么是临界资源呢?就是一个大家都可以访问的资源。比如PC的屏幕,同一时刻只能有一个进程去使用,如果多个进程都去访问,那么屏幕就会显示的乱七八糟的,显然这不是我们想要的效果 。还有就是单核CPU的使用,也只能有一个进程拿到CPU的使用权。
b. 卖票系统:当现在系统有10张票,但是同时有20个人进行购买,此时就必须用到了互斥,因为一张票只能卖给一个人,那么此时票就是临界资源。需要用互斥机制完成正常的售票行为。
c. 生产者消费者模型

示例代码:

pthread_mutex_t lock;
std::list<int> blockQueue;
void* producer(void* arg)
{
    (void)arg;

    for(size_t i =1;i<101;++i)
    {
        sleep(2);
        //因为只有一个消费者,就不用互斥
        std::cout<<"生产者生产了编号为:"<<i<<" 的产品 !"<<std::endl;
        blockQueue.push_back(i);
    }
}

void* consumer_1(void* arg)
{
    (void)arg;
    while(1)
    {
        pthread_mutex_lock(&lock);
        if(!blockQueue.empty())
        {
            std::cout<<"消费者1消费编号为 :"<<blockQueue.front()<<" 的产品 !"<<std::endl;
            blockQueue.pop_front();
        }
        else
        {
            std::cout<<"等待生产者生产产品。。。!"<<std::endl;
        }
        pthread_mutex_unlock(&lock);
        sleep(1);
    }

}

void* consumer_2(void* arg)
{
    (void)arg;
    while(1)
    {
        pthread_mutex_lock(&lock);
        if(!blockQueue.empty())
        {
            std::cout<<"消费者2 消费编号为 :"<<blockQueue.front()<<" 的产品 !"<<std::endl;
            blockQueue.pop_front();
        }
        else
        {
            std::cout<<"等待生产者生产产品。。。!"<<std::endl;
        }
        pthread_mutex_unlock(&lock);
        sleep(1);
    }
}

int main()
{

    pthread_mutex_init(&lock,NULL);
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_create(&tid1,NULL,producer,NULL);
    pthread_create(&tid2,NULL,consumer_1,NULL);
    pthread_create(&tid3,NULL,consumer_2,NULL);

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_join(tid3,NULL);


    pthread_mutex_destroy(&lock);

    return 0;
}


//程序执行结果
[skin@localhost Text]$ ./a.out 
等待生产者生产产品。。。!
等待生产者生产产品。。。!
等待生产者生产产品。。。!
等待生产者生产产品。。。!
生产者生产了编号为:1 的产品 !
消费者2 消费编号为 :1 的产品 !
等待生产者生产产品。。。!
等待生产者生产产品。。。!
等待生产者生产产品。。。!
生产者生产了编号为:2 的产品 !
消费者2 消费编号为 :2 的产品 !
等待生产者生产产品。。。!
等待生产者生产产品。。。!
等待生产者生产产品。。。!
生产者生产了编号为:3 的产品 !
消费者2 消费编号为 :3 的产品 !
等待生产者生产产品。。。!
等待生产者生产产品。。。!
等待生产者生产产品。。。!
生产者生产了编号为:4 的产品 !
消费者2 消费编号为 :4 的产品 !
等待生产者生产产品。。。!
等待生产者生产产品。。。!
等待生产者生产产品。。。!
生产者生产了编号为:5 的产品 !
消费者1消费编号为 :5 的产品 !

1.对上面进行总结:

a. 生产者和生产者之间是互斥关系,即同时只能有一个生产者向队列中放入产品。
b. 消费者和消费者之间是互斥关系,即同时只能有一个消费者从队列中取出产品
c. 生产者和消费者是同步关系,即只有生产了产品,才可以消费,有一个顺序。

  • 09.条件变量
    -

1.条件变量就是,当一个条件满足了,才进行下面的事情,这也是进程间同步的一种有效手段。
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
2.下面对其简单试使用下

pthread_mutex_t lock;
pthread_cond_t cond;
std::list<int> blockQueue;
void* producer(void* arg)
{
    (void)arg;

    for(size_t i =1;i<101;++i)
    {
        sleep(2);
        //因为只有一个消费者,就不用互斥
        std::cout<<"producer:"<<i<<"  "<<std::endl;
        blockQueue.push_back(i);
        pthread_cond_signal(&cond);
    }
}

void* consumer_1(void* arg)
{
    (void)arg;
    while(1)
    {
        pthread_cond_wait(&cond,&lock);
        std::cout<<"consumer_1:"<<blockQueue.front()<<"  "<<std::endl;
        blockQueue.pop_front();
        sleep(1);
    }

}
void* consumer_2(void* arg)
{
    (void)arg;
    while(1)
    {
        pthread_cond_wait(&cond,&lock);
        std::cout<<"consumer_2:"<<blockQueue.front()<<"  "<<std::endl;
        blockQueue.pop_front();
        sleep(1);
    }
}

int main()
{

    pthread_mutex_init(&lock,NULL);
    pthread_cond_init(&cond,NULL);
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_create(&tid1,NULL,producer,NULL);
    pthread_create(&tid2,NULL,consumer_1,NULL);
    pthread_create(&tid3,NULL,consumer_2,NULL);

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_join(tid3,NULL);

    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&lock);

    return 0;
}

//程序执行结果
[skin@localhost Text]$ ./a.out 
producer:1  
consumer_1:1  
producer:2  
consumer_2:2  
producer:3  
consumer_1:3  
producer:4  
consumer_2:4  
producer:5  
consumer_1:5 

1.总结上述代码:

a. 我们没有用if进行判断阻塞队列中是否有产品,而是每次生产一个产品就是唤醒一个再等待的线程,这样就只有生产了产品才会去消费,这样就达到了同步,这样看起来更加优雅!
b. 为什么条件等待要加互斥锁呢?因为有多个消费者再等呢,所以要互斥呀,要不你说给谁呢?

  • 10.简述信号量
  • -

1.POSIX和System V两个版本的信号量

a. 都是一种互斥的机制
b. P操作就是申请加锁操作,V操作就是释放锁操作

2.函数简介
这里写图片描述
其中sem_wait就是P操作,申请资源,使信号量值减1,sem_post释放资源,相当于V操作,信号量加1.

  • 11.简介读写锁和自旋锁
  1. 读写锁:就是写操作是互斥的,同时只有一个线程进行写操作,但是可以多个线程完成读取操作。
  2. 自旋锁:和互斥量类似,但是它不是通过休眠使进程阻塞的,而是再获取锁之前一直处于忙等(自旋)阻塞状态,锁被持有的时间短,而且线程不希望再重新调度上花费太多成本。一般用再实现其他锁。

如果错误,可以私信我,在这里表示感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值