接下来讨论下线程。进程和线程是一个很有趣的话题,进程和线程的区别到底是什么?一些书上讲线程是“轻量级进程”,从而可以节省切换开销。但是线程到底是怎么样成为轻量级进程的呢?

可以设想这样的场景,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)相关推荐

  1. python 主程序等待 子线程_Python多线程中主线程等待所有子线程结束的方法

    Python多线程中主线程等待所有子线程结束的方法 发布时间:2020-07-30 14:39:04 来源:亿速云 阅读:77 作者:小猪 这篇文章主要讲解了Python多线程中主线程等待所有子线程结 ...

  2. python如何强制结束主线程_强制结束线程

    场景: 如果某个线程持续阻塞,无法退出,从而导致整个程序无法结束,此时就需要强制结束线程 思路:由于程序阻塞,比如卡在代码中的某一行后,一直无法向下执行,此时,无法通过常规方式结束线程 方法1: 采用 ...

  3. JAVA跨线程传递数据方式总结

    实现跨线程传递数据方式: v1:子线程使用主线程的局部变量 这种当主线程和子线程不在一快儿时就不适用.可以使用JDK原生的InheritableThreadLocal. v2:InheritableT ...

  4. 【Android】子线程切回主线程的方法梳理

    [Android]子线程切回主线程的方法梳理 view.post(Runnable action) textView.post(() -> {textView.setText("更新t ...

  5. java main 如何不退出_为什么java main主线程退出了子线程还能运行;golang main结束所有协程都被结束了...

    最近看golang main函数结束,所有协程都被结束了 结论是这样:A不是main程的情况下,在A程里开启B程,A程执行完,A程return之后,B程不受影响,不会挂掉.所有子协程与main程同级的 ...

  6. 在主线程中为子线程解锁_在XP中为Google Chrome启用Vista黑色风格主题

    在主线程中为子线程解锁 If you've seen the screenshots of Google Chrome on XP vs Vista, you've probably noticed ...

  7. InheritableThreadLocal类原理简介使用 父子线程传递数据详解 多线程中篇(十八)...

    上一篇文章中对ThreadLocal进行了详尽的介绍,另外还有一个类: InheritableThreadLocal 他是ThreadLocal的子类,那么这个类又有什么作用呢? 测试代码 publi ...

  8. Java多线程、主线程等待所有子线程执行完毕、共享资源

    1.Java创建与启动线程 Java提供两种方式创建和启动线程:1.直接Thread类,2.实现Runable接口. 1.1  继承Thread类 public class myThread exte ...

  9. 主线程和子线程的关系(讨论主线程结束,子线程是否要回收)

    主线程和子线程,在操作系统里面其实是一样的,没有本质区别.至于主线程结束,子线程是否要回收,下面有两种比较特殊的情况. 第一种: 主线程退出了,子线程还可以执行. 主要是因为pthread_exit函 ...

最新文章

  1. Poj-1088-滑雪
  2. ubyntu 链接mysql_ubuntu mysql 的安装、配置、简单使用,navicat 连接
  3. html不可选择的按钮,HTML功能无法使用按钮
  4. SVN:This client is too old to work with working copy…解决方法
  5. 创建react应用程序_通过构建电影搜索应用程序在1小时内了解React
  6. GIT学习(二)--Git分布式的好处
  7. 每周荐书:Kotlin、分布式、Keras(评论送书)
  8. RDD DataFrame DataSet 区别和转换
  9. python经典题库及答案文库_Python经典题库及答案
  10. 微信开放平台:网站应用-微信登录
  11. android时间格式am pm,pm时间(am.pm正确时间书写格式)
  12. 二十款漂亮CSS字体样式
  13. 文件服务器异地容灾,三种异地容灾方案(完整版).pdf
  14. 用C++写一个简单小病毒(零基础奶妈级教学,不可能学完还不懂)
  15. echarts数据可视化系列:仪表盘
  16. 计算机工具栏图标素材,设计软件工具栏图标icon
  17. 代码审计:企业级web代码安全架构读书笔记(一)
  18. IBM合作伙伴世界峰会:将全部筹码都押在认知计算上
  19. 神经网络的图像识别技术,神经网络的层数怎么看
  20. 安全技术1 网络安全基础

热门文章

  1. 【免费下载】2021年9月热门报告盘点(附热门报告列表及下载链接)
  2. 【报告分享】面向数据流的产品迭代及业务闭环.pdf
  3. 绝对不能错过!计算机视觉Polygon Mesh Processing读书笔记——3
  4. 腾讯广告算法大赛 | 萌新粉丝投稿讲述数据竞赛小白观赛心得
  5. 机器学习深度学习知识点总结
  6. php.ini 是否设置路由,php – 如何在路由INI文件中为Zend Framework中的子域编写路由链?...
  7. docker mysql主从复制
  8. 写作就像升级打怪,4个实战技巧让你“写什么都很棒”!
  9. undefined reference to symbol' pthread_create@@GLIBC_2.2.5'
  10. TaskBarProgress(任务栏进度条)