Linux下对线程的认识+生产消费者模型+信号量

本文围绕Linux线程展开,介绍了线程概念,指出其是CPU调度基本单位,创建本质是分配资源。阐述了线程的互斥与同步,包括加锁、条件变量的使用。还讲解了生产消费者模型的实现与高效原因,以及POSIX信号量在其中的应用,用于解决共享资源访问问题。

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

线程的概念

线程是进程内部中更加轻量化的一种执行流。线程是CPU调度的基本单位,而进程是承担系统资源的实体。就是说一个进程中可能会有多个线程,而在Linux内核中并没有真正重新的创建线程并重新进行资源分配,因为我们每个线程指向的资源都是一样的,都在进程的地址空间空间中,所以Linux内部的线程创建本质就是创建PCB结构体(称作TCB),并指向同一个进程地址空间。可以说线程的创建其实就是进行资源的分配。

线程的理解 

int gav=100;
void *ThreadRoutine(void *arg)
{
    const char *threadname = (const char *)arg;
    while (true)
    {
        cout << "I am a new thread: " << threadname<<" gav = "<<gav<<" &gav = "<<&gav<<endl;
        gav--;
        sleep(1);
    }
}
int main()
{
    // 已经有进程了
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread 1");

    // 主线程
    while (true)
    {
        cout << "I am main thread"<<" gav = "<<gav<<" &gav = "<<&gav<<endl;
        sleep(1);
    }
    return 0;
}

我们多个线程的全局数据是共享的,所以尽管创建了新线程,也是可以访问全局数据的。 

 我们的主线程LWP和PID的值是相等的,而LWP就是(light weight process)轻量级进程的缩写,我们CPU进行调度时看的就是LWP。其实g++编译线程相关程序的过程其实是需要带上库名的: -l pthread,其实原因也可以解释:

线程库

test.exe:test.cpp
	g++ -o $@ $^ -std=c++11 -l pthread
.PHONY:clean
clean:
	rm -f test.exe
run:
	./test.exe

我们发现当我们编写makefile文件时,在g++编译线程的程序时必须要用到-l pthread,其实就是因为我们的Linux内核中并没有创建线程的接口,只有轻量级进程的概念。(其实就是通过PCB代替了线程TCB)所以在当我们pthread_create创建线程时,本制就是在用户层和操作系统层之间封装一层软件层(pthread原生线程库),其中对上提供了线程的相关控制接口,对下就是通过调用相关轻量级进程的控制函数。

竟然这是一个库那么就好理解了:动态库和静态库的理解 Linux-优快云博客

 我们使用库文件是需要标明头文件的路径(-I),库文件的路径(-L),库名(-l),如果我们的头文件拷贝到/usr/include目录下,库文件拷贝到/lib64目录下的话就不需要指定各自路径,只需要指定库名就行,因为程序在编译的过程时会默认到以上路径下去找头文件和库文件,找不到才需要带上各自路径。

线程切换效率高

我们的线程指向的是同一个地址空间,同一张页表,所以各个线程中的固定资源都是一样的。CPU中存在着一个Cache缓存,这里面存放的就是程序所要执行的代码与数据,当执行当前代码时,进程上下文就会在Cache中找后续代码并执行,但是此过程可能会发生函数跳转,Cache失效会重行加载代码,但是根据局部性原理,大概率上下代码是连续执行的。所以我们的线程切换是不用切换Cache的。而且我们知道进程间是独立的,数据都是各自私有,所以线程切换相对于进程而言所切换的寄存器内容更少。

线程出现问题,进程就终止

int gav=100;
void *ThreadRoutine(void *arg)
{
    const char *threadname = (const char *)arg;
    while (true)
    {
        
        cout << "I am a new thread: " << threadname<<" gav = "<<gav<<" &gav = "<<&gav<<endl;
        gav--;
        sleep(1);
        int a=3;
        a/=0;
    }
}
int main()
{
    // 已经有进程了
    pthread_t tid_1,tid_2,tid_3;
    pthread_create(&tid_1, nullptr, ThreadRoutine, (void *)"thread 1");
    pthread_create(&tid_2, nullptr, ThreadRoutine, (void *)"thread 2");
    pthread_create(&tid_3, nullptr, ThreadRoutine, (void *)"thread 3");

    // 主线程
    while (true)
    {
        cout << "I am main thread"<<" gav = "<<gav<<" &gav = "<<&gav<<endl;
        sleep(1);
    }
    return 0;
}

其实就是因为线程中对每种信号的处理方式都是共享的,也就是handler表共享,所以一个线程崩溃的话,其他线程就会执行相同的处理方法。 

线程终止与返回值

1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。

2.

pthread_exit函数
功能:线程终止
原型:void pthread_exit(void *value_ptr);
参数:value_ptr:value_ptr,是线程退出的返回值,不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

3.

pthread_cancel函数
功能:取消一个执行中的线程,取消后该线程的返回值设为-1(PTHREAD_CANCELED)
原型:int pthread_cancel(pthread_t thread);
参数:thread:线程ID
返回值:成功返回0;失败返回错误码

线程等待: 

pthread_join函数
功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值(输出型参数)
返回值:成功返回0;失败返回错误码
void *ThreadRoutine(void *arg)
{
    const char *threadname = (const char *)arg;
    while (true)
    {
        cout << "I am a new thread: " << threadname << endl;
        sleep(1);
        break;
    }
    static char tmp[60]; // 局部变量出栈就会销毁,设为静态
    snprintf(tmp, sizeof(tmp), "i have done:%s", threadname);
    pthread_exit((void *)tmp);
}
int main(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CR0712

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值