pthread线程传递数据回主线程_操作系统4:线程(1)
接下来讨论下线程。进程和线程是一个很有趣的话题,进程和线程的区别到底是什么?一些书上讲线程是“轻量级进程”,从而可以节省切换开销。但是线程到底是怎么样成为轻量级进程的呢?
可以设想这样的场景,word编辑。这个程序需要完成的功能想一下至少需要包括接收用户的输入、拼写检查、定期地保存。那么能不能使用一个进程完成这个工作呢?如果是一个进程,那么需要写代码的程序员考虑到什么时候切换这几个功能。很明显不是一个好选择。
那么能不能使用三个进程来完成这个工作呢?这边有点问题。就是进程之间的资源共享。我们知道进程有自己的一套资源,进程之间的资源不是共享的,被编辑的文件也是进程的资源【也正因为进程独占资源较多,所以是重量级的,切换代价高】。如何体现进程之间不共享资源呢?如果我们使用word进行编辑,那么可以试着两次打开一个word文档,然后同时编辑,结果呢?可以再看一个代码:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int count = 13;
int main(){pid_t pid;
// int count = 13;pid = fork();if(pid == 0){sleep(5);count = 31;}else if (pid > 0){wait(NULL);}else printf("Fork Failure!n");printf("There are %d apples!n",count);exit(0);
}
可以看到一个进程改变了全局变量,对另一个进程毫无影响。而在上面的应用中,我们很明显需要多个动作同时操作同一个对象。所以这个时候需要线程。线程的作用,就是在一个进程中,同时有多个动作可以操作同一个对象。线程共享很多的资源,仅仅维持可以保证自己动作顺利运行的一些资源,譬如寄存器的值,局部的变量(需要有自己的栈)等。因为共享了资源,所以本身的资源量比较少,那么切换也比较容易。
这样的话,线程之间可以共同操作一个全局变量。
进程和线程的资源比较:
可以看一下线程的代码。C语言中的线程需要用到了一个专门的库pthread。要使用pthread,需要包含pthread.h并且需要使用-pthread(或-lpthread)编译器选项进行编译。 此选项告诉编译器程序需要线程支持要创建线程,请使用函数pthread_create。 这个函数有四个参数:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
- 第一个是指向一个变量的指针,该变量将保存新创建的线程的id。
- 第二个是指向属性的指针,可以使用它来调整和调整pthreads的一些高级功能。
- 第三个是指向想要运行的函数的指针
- 第四个是函数参数的指针
参数void *(* start_routine)(void *)很难读懂, 它表示一个带有void *指针并返回void *指针的指针。 结合例子来看就很容易了:
#include<pthread.h>
#include<stdlib.h>
#include<stdio.h>#define NUMBER_OF_THREADS 10void * print_hello_world(void *tid){printf("Hello world, greetings from thread %dn",tid);pthread_exit(NULL);
}int main(){pthread_t threads[NUMBER_OF_THREADS];int status, i;void * val;for(i=0;i<NUMBER_OF_THREADS;i++){printf("main here, creating threads %dn",i);status = pthread_create(&threads[i],NULL,print_hello_world,(void *)i);if(status != 0){printf("oops,code error %d",status);exit(-1);}}for(i=0;i<NUMBER_OF_THREADS;i++)pthread_join(threads[i],&val);exit(0);
}
上面的代码中,创建了10个线程,每个线程都去打印hello world。所以可以看到第三个参数就是我们自己准备好的print_hello_world()函数,同时希望打印出来是第几个线程,所以,第四个参数中把线程的编号传了进去。
看一下运行的效果:
可以看出,主线程按顺序创建了10个子线程,但是子线程的运行是乱序的。多运行几次,子线程的运行顺序也不同。
上面的代码中使用了pthread_join,pthread_join使一个线程等待另一个线程结束。
代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
作为对比,请大家运行一下以下的代码:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>void* myfunc(void* ptr) {int i = *((int *) ptr);printf("%d n", i);return NULL;
}int main() {// Each thread gets a different value of i to processint i;void * val;pthread_t tid[10];for(i =0; i < 10; i++) {pthread_create(&tid[i], NULL, myfunc, &i); // ERROR}//pthread_exit(NULL);for(i=0;i< 10;i++)pthread_join(tid[i],&val);exit(0);}
可以先分析下,这份代码的运行结果。以及
void* myfunc(void* ptr) {int i = *((int *) ptr);printf("%d n", i);return NULL;
}int main() {// Each thread gets a different value of i to processint i;void * val;pthread_t tid[10];for(i =0; i < 10; i++) {pthread_create(&tid[i], NULL, myfunc, &i); // ERROR}pthread_exit(NULL);//for(i=0;i< 10;i++)// pthread_join(tid[i],&val);exit(0);}
以及,如果希望它能够正常打印出来0,1,2,...,9,可以怎么样改这份代码。
上面的代码主要是了解一下线程的运行情况,下面我们来讨论一下线程对全局变量的共享
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>int i=10;
void * fn1 (){printf("thread1: i=%dn",(int)i);
}void * fn2()
{sleep(1);i++;printf("thread2: i=%dn",(int)i);
}int main(){pthread_t t1,t2;void * val;pthread_create(&t1,NULL,fn1,NULL);pthread_create(&t2,NULL,fn2,NULL);pthread_join(t1,&val);pthread_join(t2,&val);printf("main process i=%dn",i);return 0;
}
在上面的代码中,i是全局变量,所有的线程都可见,而且可以直接对全局变量进行修改。
下面这份代码的输出会是什么样子呢?
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>void * fn1 (void * i){int j = (*((int *)i))++ ;printf("thread1: j=%dn",j);
}void * fn2(void * i)
{sleep(1);int j = *((int *)i) + 1;printf("thread2: j =%dn",j);
}int main(){pthread_t t1,t2;void * val;int i = 10;pthread_create(&t1,NULL,fn1,(void *)(&i));pthread_create(&t2,NULL,fn2,(void *)(&i));pthread_join(t1,&val);pthread_join(t2,&val);printf("main process i=%dn",i);return 0;}
写局部变量的代码的时候发现的有趣的代码,发出来请大家一起讨论下。
最后我们讨论下多线程中比较容易出错的地方。
有一道面试题是这样的:
一个全局变量tally,两个线程并发执行(代码段都是ThreadProc),问两个线程都结束后,total的取值范围。
int total = 0;//global
void ThreadProc()
{for(inti = 1; i <= 50; i++)total += 1;
}
理解这个题目的答案是一个范围,而不是100,首先要了解的是,线程发生调度完全取决于操作系统;另外,total +=1,在机器指令层面,并不是一条指令,而是三条指令,首先要将变量的值保存到寄存器中,然后对寄存器进行操作,最后保存回变量。类似于下面:
tmp = total;
tmp = tmp + 1;
total = tmp;
这道面试题考的就是要详细分析多种可能发生的调度情况。譬如我们可以很容易找到一种调度的情况,使得total的结果是75。
进程1,首先得到调度,然后执行使得total的值变成25,此时发生了调度,由进程2执行;
进程2开始执行,读取total的值,保存到自己的tmp中,此时进程2的tmp=25;然后再次发生调度;
进程1接着执行完毕,在进程1的操作下,total变成了50,进程1退出。
进程2被调度,执行,此时它不会去重新读取新的total的值,而是直接使用自己已经保存的tmp的值开始执行指令,50次循环之后变成了75。
total的值也就是75。
75只是一个例子;那么total的范围到底应该是多少呢?
考虑下面的调度情况,total的值是多少?
进程1调度,保存tmp=0;
进程2调度,执行49次,此时total=49;
进程1调度,执行一次,修改total的值;
进程2调度,读取total的值准备进行最后一次计算,保存到tmp;
进程1调度,执行剩下的49次循环;
进程2调度,完成1次计算。
本门课程在B站有视频。一些问题会在视频中解答。:)
pthread线程传递数据回主线程_操作系统4:线程(1)相关推荐
- python 主程序等待 子线程_Python多线程中主线程等待所有子线程结束的方法
Python多线程中主线程等待所有子线程结束的方法 发布时间:2020-07-30 14:39:04 来源:亿速云 阅读:77 作者:小猪 这篇文章主要讲解了Python多线程中主线程等待所有子线程结 ...
- python如何强制结束主线程_强制结束线程
场景: 如果某个线程持续阻塞,无法退出,从而导致整个程序无法结束,此时就需要强制结束线程 思路:由于程序阻塞,比如卡在代码中的某一行后,一直无法向下执行,此时,无法通过常规方式结束线程 方法1: 采用 ...
- JAVA跨线程传递数据方式总结
实现跨线程传递数据方式: v1:子线程使用主线程的局部变量 这种当主线程和子线程不在一快儿时就不适用.可以使用JDK原生的InheritableThreadLocal. v2:InheritableT ...
- 【Android】子线程切回主线程的方法梳理
[Android]子线程切回主线程的方法梳理 view.post(Runnable action) textView.post(() -> {textView.setText("更新t ...
- java main 如何不退出_为什么java main主线程退出了子线程还能运行;golang main结束所有协程都被结束了...
最近看golang main函数结束,所有协程都被结束了 结论是这样:A不是main程的情况下,在A程里开启B程,A程执行完,A程return之后,B程不受影响,不会挂掉.所有子协程与main程同级的 ...
- 在主线程中为子线程解锁_在XP中为Google Chrome启用Vista黑色风格主题
在主线程中为子线程解锁 If you've seen the screenshots of Google Chrome on XP vs Vista, you've probably noticed ...
- InheritableThreadLocal类原理简介使用 父子线程传递数据详解 多线程中篇(十八)...
上一篇文章中对ThreadLocal进行了详尽的介绍,另外还有一个类: InheritableThreadLocal 他是ThreadLocal的子类,那么这个类又有什么作用呢? 测试代码 publi ...
- Java多线程、主线程等待所有子线程执行完毕、共享资源
1.Java创建与启动线程 Java提供两种方式创建和启动线程:1.直接Thread类,2.实现Runable接口. 1.1 继承Thread类 public class myThread exte ...
- 主线程和子线程的关系(讨论主线程结束,子线程是否要回收)
主线程和子线程,在操作系统里面其实是一样的,没有本质区别.至于主线程结束,子线程是否要回收,下面有两种比较特殊的情况. 第一种: 主线程退出了,子线程还可以执行. 主要是因为pthread_exit函 ...
最新文章
- Poj-1088-滑雪
- ubyntu 链接mysql_ubuntu mysql 的安装、配置、简单使用,navicat 连接
- html不可选择的按钮,HTML功能无法使用按钮
- SVN:This client is too old to work with working copy…解决方法
- 创建react应用程序_通过构建电影搜索应用程序在1小时内了解React
- GIT学习(二)--Git分布式的好处
- 每周荐书:Kotlin、分布式、Keras(评论送书)
- RDD DataFrame DataSet 区别和转换
- python经典题库及答案文库_Python经典题库及答案
- 微信开放平台:网站应用-微信登录
- android时间格式am pm,pm时间(am.pm正确时间书写格式)
- 二十款漂亮CSS字体样式
- 文件服务器异地容灾,三种异地容灾方案(完整版).pdf
- 用C++写一个简单小病毒(零基础奶妈级教学,不可能学完还不懂)
- echarts数据可视化系列:仪表盘
- 计算机工具栏图标素材,设计软件工具栏图标icon
- 代码审计:企业级web代码安全架构读书笔记(一)
- IBM合作伙伴世界峰会:将全部筹码都押在认知计算上
- 神经网络的图像识别技术,神经网络的层数怎么看
- 安全技术1 网络安全基础
热门文章
- 【免费下载】2021年9月热门报告盘点(附热门报告列表及下载链接)
- 【报告分享】面向数据流的产品迭代及业务闭环.pdf
- 绝对不能错过!计算机视觉Polygon Mesh Processing读书笔记——3
- 腾讯广告算法大赛 | 萌新粉丝投稿讲述数据竞赛小白观赛心得
- 机器学习深度学习知识点总结
- php.ini 是否设置路由,php – 如何在路由INI文件中为Zend Framework中的子域编写路由链?...
- docker mysql主从复制
- 写作就像升级打怪,4个实战技巧让你“写什么都很棒”!
- undefined reference to symbol' pthread_create@@GLIBC_2.2.5'
- TaskBarProgress(任务栏进度条)