详解【多线程与并发】之线程

目录

1、线程

1.1线程Thread

1.2线程特点

1.3线程函数的原型

1.4Linux对于pthread的API的支持

1.4.1创建一个线程

1.4.2线程的退出

1.5资源分离

2、线程的同步/互斥机制

2.1线程互斥锁

2.1.1初始化线程互斥锁

2.2线程互斥锁的PV 操作

2.2.1P 操作(上锁操作)

2.2.2V 操作(解锁操作)

2.2.3线程互斥锁的销毁操作

3、生产者消费者模型

4、线程条件变量

4.1线程条件变量API

4.1.1初始化/销毁条件变量

4.1.2等待一个条件变量

4.1.3唤醒线程/触发条件变量


谢谢帅气美丽且优秀的你看完我的文章还要点赞、收藏加关注

没错,说的就是你,不用再怀疑!!!

希望我的文章内容能对你有帮助,一起努力吧!!!


1、线程

前面讲到进程:为了并发执行任务(程序),现代操作系统才引进 进程 的概念

分析:

  • 创建开销问题:创建一个进程开销:大
    • 子进程需要拷贝父进程的整个地址空间
  • 通信开销问题:进程间的通信
    • 需要用第三方(如:内核
    • P1 -> copy -> 内核 -> copy -> P2
    • 进程间通信代价或者开销也是很大的。进程的地址空间是独立,要通信的话需要用第三方的空 间。

于是,就有人提出能不能在 同一个(同一个进程内部)进程地址空间中进行任务的并发:线程/轻量级进程

1.1线程Thread

线程是一个比进程更小的活动单位。它是进程中的执行路径(执行分支),线程也是并发的一种形式。 进程内部可以存在多个线程,它并发执行,但是进程内部的所有的线程共享整个进程的地址空间

main 函数:进程的主线程

1.2线程特点

  • 创建一个线程要比创建进程开销要小很多
    • 因为创建一个线程的话,不需要拷贝进程的地址空间
  • 实现线程间的通信会更加方便
    • 因为进程内部所有线程共享整个进程地址空间
  • 线程也是一个动态概念:
    • 线程(进程)状态图        
      • 就绪态: ready
      • 运行态: running
      • 阻塞态: blocking
  • 有了线程的概念之后
    • 系统的调度单位就从进程变为线程,资源的分配还是以进程为单位

线程是进程内部的一个指令的执行分支,多个线程就是多个指令序列并发执行。这些指令必须在函数 内部,线程的指令部分肯定是封装一个函数的内部的。这个函数,就称之为:线程函数,一个线程在创 建之后,要执行的指令全部封装在该函数内部,这个线程函数执行完毕之后,该线程的任务也就执行完 了。

1.3线程函数的原型

Thread 的实现有多种,比较常见的为 POSIX 线程: pthread

1.4Linux对于pthread的API的支持

1.4.1创建一个线程

pthread_create :创建一个线程(启动一个线程)

#include <iostream>
#include <pthread.h>
#include <unistd.h>


static int st_val = 0;

/*
    线程函数:用于新线程创建之后调用的
*/
void *new_thread(void *data)
{
    for(;st_val < 20;st_val++)
    {
        sleep(1);
    }

    st_val = 0;
    pthread_exit(&st_val);
}


int main(int argc,const char *argv[])
{
    // 定义线程id的变量
    pthread_t tid;
        
    // 创建线程
    int ret = pthread_create(&tid,NULL,new_thread,NULL);
    if(ret == -1)
    {
        perror("线程创建失败:");
        return -1;
    }

    // 等待子线程把st_val累加到一百
    while(1)
    {
        if(st_val >= 20)
            break;
        std::cout << "st_val:" << st_val << std::endl;
        sleep(1);
    }

    return 0;
}

1.4.2线程的退出

  • 线程函数的退出(线程函数的返回)
  • 在线程执行的任意时刻调用 pthread_exit
  • 被别人干掉
    • cancel :被别人取消(其他线程调用 pthread_cancel )
      • t1:pthread_cancel(t2)
        • t1 调用取消函数,取消 t2 , t2 不一定会被取消
          • 因为 t2 能不能被其他线程取消,取决于 t2 线程的一个属性: 取消属性
            • 它是否可以被 cancelled
      • #include <iostream>
        #include <pthread.h>
        #include <unistd.h>
        
        
        
        static int st_val = 0;
        
        /*
            线程函数:用于新线程创建之后调用的
        */
        void *new_thread(void *data)
        {
            int oldstate;
            pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,&oldstate);
        
            for(;st_val < 20;st_val++)
            {
                sleep(1);
            }
        
            st_val = 0;
        }
        
        int main(int argc,const char *argv[])
        {
                
            // 定义线程id的变量
            pthread_t tid;
                
            // 创建线程
            int ret = pthread_create(&tid,NULL,new_thread,NULL);
            if(ret == -1)
            {
                perror("线程创建失败:");
                return -1;
            }
        
            // 等待子线程把st_val累加到一百
            while(1)
            {
                if(st_val >= 20)
                    break;
                std::cout << "st_val:" << st_val << std::endl;
        
                if(st_val == 5)
                {
                    pthread_cancel(tid);
                    break;
                }
        
                sleep(1);
            }
            return 0;
        }
        

      • 这个属性叫做: 可以被取消属性
        • PTHREAD_CANCEL_ENABLE :表示该线程可以被取消
        • PTHREAD_CANCEL_DISABLE :表示该线程不能被取消

一个线程退出了,并不是所有资源都会释放。一个线程的退出,它资源释放全部被释放,取决于一个属 性

1.5资源分离

  • detach :分离属性:
    • ENABLE : 分离资源属性
      • 该线程结束,它的所有资源都会自动释放。
    • DISABLE : 不分离资源
      • 该线程结束,会有部分资源不会自动释放,需要其他线程调用 pthread_join 这个函数 才能完全释放。
    • 默认是不分离属性。
#include <iostream>
#include <pthread.h>
#include <unistd.h>

static int st_val = 0;

/*
    线程函数:用于新线程创建之后调用的
*/
void *new_thread(void *data)
{
    // 进入线程函数第一时间就设置分离属性
    // pthread_detach(pthread_self()); // 设置了资源分离一般就不需要join了

    for(;st_val < 20;st_val++)
    {
        sleep(1);
    }

    st_val = 0;
    pthread_exit(&st_val);
}

int main(int argc,const char *argv[])
{
    // 定义线程id的变量
    pthread_t tid;
        
    // 创建线程
    int ret = pthread_create(&tid,NULL,new_thread,NULL);
    if(ret == -1)
    {
        perror("线程创建失败:");
        return -1;
    }    

    std::cout << "等待线程结束"<< std::endl;
    int *tid_ret_val = NULL;
    pthread_join(tid,(void**)&tid_ret_val);

    std::cout << "return value :" << *tid_ret_val << std::endl;
    return 0;
}

2、线程的同步/互斥机制

为了线程之间,能够去有序的访问共享资源,引用 信号量机制

  • 信号量 : System V 和 POSIX 信号量
  • 线程互斥锁

2.1线程互斥锁

线程互斥锁也是 信号量 ,只不过线程互斥锁,存在于进程地址空间,用于线程间同步和互斥操作,线程 互斥锁它的效率相对信号量来说要高。

线程互斥锁:使用 pthread_mutex_t 的类型来描述一个锁

安装线程 POSIX 帮助手册: sudo apt-get install manpages-posix-dev // 安装posix 帮助手册

2.1.1初始化线程互斥锁

2.2线程互斥锁的PV 操作

2.2.1P 操作(上锁操作)

2.2.2V 操作(解锁操作)

2.2.3线程互斥锁的销毁操作

#include <iostream>
#include <pthread.h>
#include <unistd.h>

// 模拟内存
static int memory = 100;

// 互斥锁
pthread_mutex_t  mutex;


// 线程函数模拟病毒
void *thread_trylock(void *data)
{
    // 等待一秒,防止线程一进来就直接上锁
    sleep(1);

    // 记录自己抢占了多内存
    int self_memory = 0;
    // 循环条件
    while(memory > 0)
    {
        // 上锁
        if(0 == pthread_mutex_trylock(&mutex))
        {
            // 中间的代码就是临界区
            memory -= 10;
            self_memory += 10;

            sleep(1);
            // 解锁
            pthread_mutex_unlock(&mutex);
            std::cout << "嘿嘿!咱“尝试上锁病毒” 抢占了10字节内存!" << std::endl;
        }
        else
            std::cout << "哎!咱“尝试上锁病毒”没有抢内存!" << std::endl;

        sleep(1);
    }
    std::cout << "嘿嘿!咱“尝试上锁病毒” 总共抢占了"<< self_memory <<"字节内存!" << std::endl;
    return nullptr;
}

void *thread_lock(void *data)
{
    // 等待一秒,防止线程一进来就直接上锁
    sleep(1);

    // 记录自己抢占了多内存
    int self_memory = 0;
    // 循环条件
    while(memory > 0)
    {
        // 上锁
        pthread_mutex_lock(&mutex);
        // 中间的代码就是临界区
        memory -= 10;
        self_memory += 10;

        sleep(1);
        // 解锁
        pthread_mutex_unlock(&mutex);
        std::cout << "嘿嘿!咱“死锁上锁病毒” 抢占了10字节内存!" << std::endl;
        sleep(1);
    }
    std::cout << "嘿嘿!咱“死锁上锁病毒” 总共抢占了"<< self_memory <<"字节内存!" << std::endl;
    return nullptr;
}

void *thread_timedlock(void *data)
{
    // 等待一秒,防止线程一进来就直接上锁
    sleep(1);

    //等待的时间
    struct timespec tm;
    tm.tv_sec = 1;
    tm.tv_nsec = 0;

    // 记录自己抢占了多内存
    int self_memory = 0;
    // 循环条件
    while(memory > 0)
    {
        // 上锁
        if(0 == pthread_mutex_timedlock(&mutex,&tm))
        {
            // 中间的代码就是临界区
            memory -= 10;
            self_memory += 10;

            sleep(1);
            // 解锁
            pthread_mutex_unlock(&mutex);
            std::cout << "嘿嘿!咱“等待上锁病毒” 抢占了10字节内存!" << std::endl;
            
        }
        else
        {
            std::cout << "哎!难受  咱“等待上锁病毒” 等了一秒都没有抢占到内存!" << std::endl;
        }
        sleep(1);
    }
    std::cout << "嘿嘿!咱“等待上锁病毒” 总共抢占了"<< self_memory <<"字节内存!" << std::endl;
    return nullptr;
}

int main()
{
    pthread_mutex_init(&mutex,NULL);

    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    
    // 启动线程
    pthread_create(&tid1,NULL,thread_lock,NULL);
    pthread_create(&tid1,NULL,thread_trylock,NULL);
    pthread_create(&tid1,NULL,thread_timedlock,NULL);


    // 阻塞等待子线程结束
    // pthread_join(tid1,NULL);
    // pthread_join(tid2,NULL);
    // pthread_join(tid3,NULL);
    while(1);
    pthread_mutex_destroy(&mutex);
    return 0;
}

3、生产者消费者模型

生产者消费者模型:利用厂商和消费者关系,由生产者线程进行生产(产生任务),再由消费者线程消 费(执行任务),没有任务的时候,消费者等待生产者产生任务。

  • 共享资源的互斥访问问题
    • 信号量/线程互斥锁
  • 当缓冲区(生产者没有产出的时候)没有数据的时候,(消费者)应该怎么办?
    • 1. 不停的去测试,看有没有数据。
      • 轮询访问,但是轮询有缺陷:一直在访问,浪费CPU资源。轮询有时间差,占用总线: Is always busy
    • 让出CPU,当有数据的时候,再唤醒我( wake up ),线程条件变量同步

4、线程条件变量

线程条件变量:在多线程程序设计中,可以用 条件变量 为表示一个特定的条件或者是事件

pthread_cond_t :来描述一个条件变量(类型)

至于条件变量,到底是一个什么事件或者说表示一个什么条件?完全由程序猿去解释这个条件变量所代 表的含义

在条件变量上的三种操作:

  • 初始化
  • 等待一个条件变量(等待该条件变量所表示的事件
  • 唤醒一个线程/触发条件变量(唤醒了正在等待该事件的线程

4.1线程条件变量API

4.1.1初始化/销毁条件变量

4.1.2等待一个条件变量

4.1.3唤醒线程/触发条件变量

注意:广播唤醒和单个唤醒的区别

  • 广播唤醒:唤醒所有等待的线程,去执行任务,但是任务可能不够分,那么没分到的线程继续休眠
  • 单个唤醒:随机唤醒一个线程执行任务,其他线程继续休眠。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
using std::vector;

// 线程互斥锁
pthread_mutex_t mutex;

// 条件变量
pthread_cond_t cond;

// 公共资源
FILE *file=NULL;
std::string str;

// 消费者
void *NiuMa(void *data)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        // 等待条件变量:任务
        pthread_cond_wait(&cond,&mutex); // 这里卡死不是循环的卡死,休眠,让出了CPU
        
        if(str == "退出")
        {
           pthread_mutex_unlock(&mutex);     

            pthread_exit(NULL);
        }

        std::cout << fwrite(str.c_str(),str.size(),1,file) << std::endl;
        
        pthread_mutex_unlock(&mutex);

        std::cout << pthread_self() << "线程:成功写入内容:<" << str << "> "<< std::endl;
    }

}

// 主线程:生产者
int main()
{
    // 初始化
    pthread_mutex_init(&mutex,NULL);
    pthread_cond_init(&cond,NULL);

    // 打开文件
    file = fopen("./1.txt","w+");

    vector<pthread_t> tids(10);

    // 创建10个线程
    for(int i = 0;i < 10;i++)
    {
        pthread_create(&tids[0],NULL,NiuMa,NULL);
    }

    // 通过输入来发布任务
    while(1)
    {
        std::cout << "请输入内容:" ;

        pthread_mutex_lock(&mutex);
        std::cin >> str;
        pthread_mutex_unlock(&mutex);

        if(str == "退出")
        {
            pthread_cond_broadcast(&cond);
            break;
        }

        // 唤醒线程
        pthread_cond_signal(&cond);
        sleep(1);
    }

    for(int i = 0;i < 10;i++)
    {
        pthread_join(tids[0],NULL);
    }

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    fclose(file);
    return 0;
}

  • 28
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值