初识linux之线程控制

文章详细介绍了Linux中POSIX线程库的使用,包括线程创建、线程终止、线程等待等关键操作,并通过示例代码分析了多线程编程中常见的问题和解决方案,如线程安全、线程等待和资源回收。同时,文章提到了C++11中线程的支持,并讨论了线程ID的本质和线程局部存储的概念。
摘要由CSDN通过智能技术生成

目录

一、POSIX线程库

二、线程创建

1.创建线程的接口

2. 错误的创建多线程

3.正确的创建多线程

4. 线程的私有栈结构

三、线程终止

1. 函数结束

2. 调用pthread_exit()终止

3.调用pthread_cancel()函数

四、线程等待

1. 线程等待函数

 2. 线程阻塞式等待

3.分离线程

 4. 线程的退出信息

五、C++11中的线程

六、线程id问题

七、主线程与其他线程单独拥有共享资源

八、自行封装一个简单的线程类


一、POSIX线程库

大家知道,在linux中因为linux 线程方案的影响,它是没有真正意义上的线程的,所以linux只提供了创建轻量级进程的系统接口,而没有提供创建线程的系统接口。但是CPU和用户(程序员)又只认线程,所以linux中就必须要有能够创建线程的方法。因此,就有人在linux中提出了“线程库”的概念,线程库是一个动态库,对linux中的关于轻量级进程的接口及其他内容进行了封装,通过使用线程库的方式让其他人能够在linux中模拟创建出线程以供使用,究其本质,依然是“轻量级进程”。

在linux中的线程库是POSIX标准下的线程库,它与线程有关的函数构成了一个完整的系列,绝大多数函数名都是有“pthread_”开头的。

如果要使用这些函数库,就要引进<pthread.h>头文件

当使用了线程库内的函数后,在链接时要加上“-l pthread”命令来链接线程库。

二、线程创建

1.创建线程的接口

要创建一个线程,直接使用“pthread_create()”接口即可。

 其中的第一个参数thread返回线程id,当然,实际打印出来后其实是一个地址

第二个参数attr是设置线程的属性,一般不用管,直接设置为nullptr即可。

第三个参数是一个函数地址,表示线程启动后要执行的函数

第四个参数arg是传给线程启动函数的参数。这些内容在上一篇文章“线程的基本概念”都有所涉及,就不再过多讲解。

该函数成功时返回0,失败时会返回错误码。

2. 错误的创建多线程

利用pthread_create()接口,创建5个线程:

运行该程序: 

可以正常运行,但是仔细观察后就可以发现,这里显示的线程名字,全部都是5。这就很奇怪了,在上面我们明明是创建了5个线程,那这里为什么只显示一个线程呢?修改程序如下:

仅仅向创建下次的代码块中加入一个sleep(1),让每个线程创建完后休眠1s。再运行该程序:

此时就可以看见5个线程的创建了。

那么,为什么会出现这种情况呢?首先要知道,在上述程序中,传给线程启动函数的参数,其实是“缓冲区的起始地址”,并不是直接将缓冲区内的内容传过去。

其次,如果一个父进程中创建了一个子进程,那么子进程和父进程谁先运行呢?答案是我们并不确定。因为这两个进程的运行顺序完全由调度器决定,我们并不知道调度器会先调度哪个进程。而我们知道,CPU调度的基本单位是线程,因此,当一个线程被创建出来后,新线程其该进程内的其他线程相比,谁先运行我们也并不知道。

因此,就可能会出现,当一个线程被创建出来后,刚把缓冲区的地址传给启动函数,就被CPU切走运行主线程,此时循环到达尽头从头开始,缓冲区被销毁,重新创建缓冲区。主线程此时就会向缓冲区内写新的内容并创建新的线程,而该线程被创建出来后也仅仅是向启动函数写入缓冲区的起始地址。重复上述过程直到主线程出循环。此时就会导致所有线程或者绝大部分线程的启动函数中被传入的缓冲区起始地址对应的都是编号5,进而出现上图中一直打印编号为5的线程。其实它们并不是同一个线程,仅仅是不同的线程拿到了同一个编号

有人可能会疑惑,既然缓冲区被销毁并重新创建缓冲区,那为什么传给它们的缓冲区地址都是同一个呢?原因很简单,因为在销毁缓冲区后回到循环最上面向下运行的过程中,并没有创建原来不在这个循环中的新的内容,因此,极大概率循环下去时该循环中的变量会在原地址重新生成对应变量,包括缓冲区。

当在程序中加上了一个sleep(1)后,主线程创建完新线程后就会休眠被挂起,在这个过程中,新线程的启动函数就已经通过缓冲区起始地址拿到了对应数据,因此不再受缓冲区内容更改的影响。 

因此,在创建多线程时,其实上面的方法是有问题的,在实际中最好不要用这种方式创建多线程。

3.正确的创建多线程

在上面单纯的使用循环创建新线程时,之所以会出现传入的数据有错误的情况根本原因,还是因为这些线程共享了同一个namebuffer。所以,为了避免出现这种情况,就可以通过对要传给新线程的数据进行封装和单独new空间,让不同的线程拥有属于自己的那么buffer:

通过这种方式,传递给线程启动参数的地址就是new出来的新空间的地址,而new出来的空间存放在堆上,不受作用域的影响,只在显式调用delete或程序结束时才会销毁。因此不受循环结束销毁数据并在重新循环在原地创建变量的影响。运行该程序:

可以看到,此时每个线程的编号就是正确的。当然,如果你想让主线程拿到新线程的数据,也是可以的。定义一个vector将新线程的指针传入即可:

运行程序后,就可以通过主线程拿到其他线程的数据了。这也就证实了进程中,线程的数据其实是共享的理论。

4. 线程的私有栈结构

现在有如下测试程序:

这个程序中创建了5个线程,且每个线程执行的都是同一个函数。 那么,当这个程序运行起来后,这些线程进入了同一个函数“start_routine()”处于什么状态呢?答案是“重入状态”,即该函数在被多个执行流反复进入

那这里就有一个问题,那么该函数到底还是可重入函数还是不可重入函数呢?如果抛开它正在进行的打印代码,它就是一个可重入函数。那此时就又有一个问题,这些线程进入的是同一个函数,那么它们按道理来讲应该就是用的同一批临时变量啊,既然是同一批临时变量,就意味着不同的线程进入执行就可能会影响到其他线程,这很明显就不是可重入函数啊。那么这些线程在进入同一个函数时,使用的究竟是不是同一批临时变量呢?修改程序如下进行测试:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值