linux远程开发——多线程开发与线程同步
目录
一、什么是线程
1、背景⬇️
2、线程的概念⬇️
3、线程的特点⬇️
1)轻型实体
2)独立调度和分派的基本单位
3)可并发执行
4)共享进程资源
二、线程与进程的区别
三、线程(pthread)相关函数
1、线程创建(pthread_create)
2、线程退出(pthread_exit)
四、代码实战
1、创建两个线程
2、解决编译出现 “xxx未定义” 的报错
windows下
linux下
3、线程同步问题
1)线程被中断
2)随机时间片轮转
4、线程互斥量(加锁)
五、总结
一、什么是线程
1、背景⬇️
60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端:1️⃣由于进程是资源拥有者,创建、撤消与切换存在 较大的时空开销,因此需要引入轻型进程;2️⃣多个进程并行资源开销会很大。
因此在80年代,出现了能 独立运行的基本单位 ——线程(Threads)。
2、线程的概念⬇️
线程(thread)是 操作系统 能够进行运算 调度 的最小单位。它被包含在 进程 之中,是 进程 中的实际运作单位。一条线程指的是 进程 中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务, 每个进程至少包含一个线程 。
3、线程的特点⬇️
1)轻型实体
线程中的实体基本上不拥有系统资源,只是有一点必不可少的: 需要能保证独立运行的资源 。
2) 独立调度和分派的基本单位
在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故 线程的切换非常迅速且开销小 (在同一进程中的)。
3)可并发执行
在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行。
4) 共享进程资源
在同一进程中的各个线程,都可以 共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的 地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址,也就是说 同一进程中线程间的数据是共享的 。
二、线程与进程的区别
- ✍️两者之间的区别可归纳为以下几点(线程🆚进程)
线程 | 进程 | |
资源共享 | 同一进程的各线程间共享资源 | 进程间资源相互独立,不共享 |
通信 | 直接读写进程数据段(如全局变量)来进行通信 | 进程间的通信(IPC通信) |
调度和切换 | 上下文切换比进程快得多 | 进程上下文切换比线程慢 |
资源占用 | 线程占用的资源少 | 进程占用的资源多 |
- 在操作系统中进程、线程间的包含关系如下图所示
📝
✏️:即一个操作系统中可以拥有多个进程,每个进程可以包含多个线程。
三、线程(pthread)相关函数
1、线程创建(pthread_create)
- 函数功能是创建一个线程
头文件👇
#include
函数原型👇
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数👇
✅thread:新线程创建成功后,保存新线程的标识符。
✅attr:设置线程的属性,一般不需要什么特殊的属性,为NULL即可。
✅start_routine:函数指针,保存函数地址,线程启动后要执行的函数。
✅arg:传给线程启动函数的参数。
返回值👇
成功:returns 0 ;
失败:returns an error number ;
2、线程退出(pthread_exit)
- 函数功能是终止调用线程并返回通过retval获得的值(如果线程可连接),该值可用于调用pthread_join(3)的同一进程中的另一个线程
头文件👇
#include
函数原型👇
void pthread_exit(void *retval);
参数👇
✅retval:终止线程后返回通过retval获得的值(如果线程可连接)。
返回值👇
😙
好了,线程使用到的相关函数就介绍到这里了,接下来通过一个代码实战来巩固消化这些知识,话不多说
四、代码实战
1、创建两个线程
- 使用pthread_create ()函数创建两个线程,并执行两个不同的函数业务
#include #include #include #include using namespace std;int number = 0;void* pthread_function(void* vo){while (1){for (int i = 1; i <= 10000; i++){if (i % 1000 == 0){cout << "线程1运行中: i = " << i << endl;}}sleep(1);}}void* pthread_function2(void* vo){while (1){number++;cout << "线程2运行中 number = " << number <<endl;sleep(1);}}int main(){//线程标识符,执行pthread_create()之后,线程标识符为0则代表线程创建成功pthread_t pthread_id1;pthread_t pthread_id2;pthread_id1 = pthread_create(&pthread_id1, NULL, pthread_function, NULL);//创建第一条线程if (pthread_id1 != 0){perror("pthread_create error");}else{cout << "线程1创建成功 pthreadid = " << pthread_id1 << endl;}pthread_id2 = pthread_create(&pthread_id2, NULL, pthread_function2, NULL);//创建第二条线程if (pthread_id2 != 0){perror("pthread_create error");}else{cout << "线程2创建成功 pthreadid2 = " << pthread_id2 << endl;}while (1) {//主线程死循环sleep(1);}return 0;}
2、解决编译出现 “xxx未定义” 的报错
windows下
- 在vs 2019下编译链接的时候会出现如下报错
- 解决方案如下
1)点击项目——>属性
2)链接器——>命令行
在其他选项输入 -pthread ,点击确定。
这时候重新编译链接就不会出现报错。
linux下
- 在linux下通过g++的方式进行编译,在用 ./ 的方式运行,也会出现未定义的情况
- 解决方案如下
将命令改成 g++ main.cpp -pthread -o main ,可以发现编译成功!!
3、线程同步问题
1)线程被中断
- 在两个线程执行的函数业务中,都进行了1秒的延时,按理说应该两个线程应该是同步执行,但是会出现线程1运行一半,然后被打断去运行线程2了,运行情况如下图所示
2)随机时间片轮转
- 出现上面的问题是因为CPU调度进程或线程的时候使用的是 时间片轮转
📝 知识点
时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾。
📝 知识点
操作系统将定期的中断当前正在执行的线程,将CPU分配给在等待队列的下一个线程。所以任何一个线程都不能独占CPU。每个线程占用CPU的时间取决于进程和操作系统。进程分配给每个线程的时间很短,以至于我们感觉所有的线程是同时执行的。
进程分配时间片给线程有几个特点:时间随机、顺序随机、次数随机
💦 了解这些之后就明白为啥线程2会执行两次,为啥线程1执行一半就去执行线程2 💦
⚠️注意
两个(或多个)线程同时执行时,经常需要访问到公共资源或代码的关键部分,这时就涉及到了线程的同步问题,我们可以通过下面两种方法来更好地控制线程的执行情况和更好地访问代码的关键部分。
1、线程信号量
2、线程互斥量
👇
- 本文暂时只介绍通过 线程互斥量 来解决线程的同步问题。
4、线程互斥量(加锁)
- 给某个对象加上一把“锁”,每次只允许一个线程去访问它
- 如果想对代码关键部分的访问进行控制,你必须在进入这段代码之前锁定一把互斥量,在完成操作后再打开它
互斥量对象用 pthread_mutex_t 表示
相关函数都在头文件里面声明
- 因此可以将上面的代码进行修改
#include #include #include #include using namespace std;int number = 0;pthread_mutex_t mux; //互斥量void* pthread_function(void* vo){while (1){pthread_mutex_lock(&mux); //加锁 防止其他线程抢夺CPU执行权for (int i = 1; i <= 10000; i++){if (i % 1000 == 0){cout << "线程1运行中: i = " << i << endl;}}pthread_mutex_unlock(&mux); //解锁 做完关键部分的业务后解锁sleep(1);}}void* pthread_function2(void* vo){while (1){pthread_mutex_lock(&mux); //加锁 防止其他线程抢夺CPU执行权number++;cout << "线程2运行中 number = " << number <<endl;pthread_mutex_unlock(&mux); //解锁 做完关键部分的业务后解锁sleep(1);}}int main(){//线程标识符,执行pthread_create()之后,线程标识符为0则代表线程创建成功pthread_t pthread_id1;pthread_t pthread_id2;pthread_id1 = pthread_create(&pthread_id1, NULL, pthread_function, NULL);//创建第一条线程if (pthread_id1 != 0){perror("pthread_create error");}else{cout << "线程1创建成功 pthreadid = " << pthread_id1 << endl;}pthread_id2 = pthread_create(&pthread_id2, NULL, pthread_function2, NULL);//创建第二条线程if (pthread_id2 != 0){perror("pthread_create error");}else{cout << "线程2创建成功 pthreadid2 = " << pthread_id2 << endl;}while (1) {//主线程死循环sleep(1);}return 0;}
- 运行结果
💦 通过“ 加锁 ”的方式,这样就不会出现某个线程执行关键业务的时候被打断的问题 💦
五、总结
- 😚看完这篇文章,我们来总结一下学习了哪些知识
✅:了解了线程的概念
✅:进程与线程的区别
✅:如何用代码创建线程
✅:随机时间片轮转相关知识
✅:线程互斥量的使用
😘
原创不易,转载请标明出处。
对您有帮助的话可以一键三连,会持续更新的(嘻嘻)。