我们已经知道如何使用进程来做一些事情了,然而 它并不是在什么地方都是最适合的。

我们看看进程的缺点是什么:

线程隆重登场

1. 如何创建线程

创建线程可以使用多种线程库,在此我们使用最流行的一种:POSIX线程库,也叫pthread。

假设有两个函数

 void * dose_do(void * a) {

     for (int i = ; i < ; i++) {
sleep();
puts("does_do");
} return NULL;
}
 void * dose_not(void * a) {

     for (int i = ; i < ; i++) {
sleep();
puts("does_not");
} return NULL;
}

这两个函数都返回了void指针,因为void指针可以指向存储器中任何数据类型的数据,线程函数的返回类必须是void *。

必须包含#include <pthread.h>头文件

我们使用pthread_create() 函数创建并运行一个线程,而且每个线程都需要把线程信息保存在一个pthread_t类型的数据中。

 // new pthread
pthread_t t0;
pthread_t t1; if (pthread_create(&t0, NULL, dose_not, NULL) == -) {
error("无法创建线程t0");
}
if (pthread_create(&t1, NULL, dose_do, NULL) == -) {
error("无法创建线程t1");
}

上边的两个函数将会独立的在线程中运行,知道结束,但是我们需要知道这两个函数什么时候结束。

我们使用pthread_join()函数等待函数结束,他会接受线程函数的返回值,并保存在一个void *类型的数据中。

那么这个函数是如何得知线程结束的呢?当得到线程函数的返回值的时候,就表明线程函数结束了。这也是为什么线程函数必须要有返回值的原因。

  void *result;
if (pthread_join(t0, &result) == -) {
error("无法回收线程t0");
}
if (pthread_join(t1, &result) == -) {
error("无法回收线程t1");
}

我们来看 全部代码

 #include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h> // 错误处理函数
void error(char *msg) {
fprintf(stderr, "Error: %s %s", msg, strerror(errno));
exit();
} void * dose_not(void * a) { for (int i = ; i < ; i++) {
sleep();
puts("does_not");
} return NULL;
} void * dose_do(void * a) { for (int i = ; i < ; i++) {
sleep();
puts("does_do");
} return NULL;
} int main(int argc, const char * argv[]) { // new pthread
pthread_t t0;
pthread_t t1; if (pthread_create(&t0, NULL, dose_not, NULL) == -) {
error("无法创建线程t0");
}
if (pthread_create(&t1, NULL, dose_do, NULL) == -) {
error("无法创建线程t1");
} void *result;
if (pthread_join(t0, &result) == -) {
error("无法回收线程t0");
}
if (pthread_join(t1, &result) == -) {
error("无法回收线程t1");
} return ;
}

结果如下

再来看下边这段代码,我们有2000000瓶啤酒,开启20条线程,看最后剩余多少瓶?

 #include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h> // 错误处理函数
void error(char *msg) {
fprintf(stderr, "Error: %s %s", msg, strerror(errno));
exit();
} int beers = ; void * drink_lots(void * a) { for (int i = ; i < ; i++) { beers = beers - ; } printf("剩余 %i 瓶啤酒 \n",beers); return NULL;
} int main(int argc, const char * argv[]) { // new pthread
pthread_t pthreads[]; printf("%i 瓶啤酒 \n",beers); for (int i = ; i < ; i++) { if (pthread_create(&pthreads[i], NULL, drink_lots, NULL) == -) {
error("无法创建线程");
}
} void *result; for (int i = ; i < ; i++) { if (pthread_join(pthreads[i], &result) == -) {
error("无法回收线程");
}
} return ;
}

运行结果

那么问题来了,为什么跟我们想要的结果不一样呢? 其实都点编程经验的人都知道,线程是不安全的。

要想解决这样的问题就要使用互斥锁

2. 用互斥锁保护线程

互斥锁必须对所有可能发生冲突的线程可见,也就是说它是一个全局变量。

创建:

pthread_mutex_t beers_lock = PTHREAD_MUTEX_INITIALIZER;

加锁

pthread_mutex_lock(&beers_lock);

解锁

pthread_mutex_unlock(&beers_lock);

我们修改上边的关于啤酒的函数为

 #include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h> // 错误处理函数
void error(char *msg) {
fprintf(stderr, "Error: %s %s", msg, strerror(errno));
exit();
} pthread_mutex_t beers_lock = PTHREAD_MUTEX_INITIALIZER; int beers = ; void * drink_lots(void * a) { for (int i = ; i < ; i++) { pthread_mutex_lock(&beers_lock);
beers = beers - ;
pthread_mutex_unlock(&beers_lock);
} printf("剩余 %i 瓶啤酒 \n",beers); return NULL;
} int main(int argc, const char * argv[]) { // new pthread
pthread_t pthreads[]; printf("%i 瓶啤酒 \n",beers); for (int i = ; i < ; i++) { if (pthread_create(&pthreads[i], NULL, drink_lots, NULL) == -) {
error("无法创建线程");
}
} void *result; for (int i = ; i < ; i++) { if (pthread_join(pthreads[i], &result) == -) {
error("无法回收线程");
}
} return ;
}

运行结果如下

每个线程中循环结束后才会打印结果,也就是说当循环完之后打印的结果就是那个时间点还剩多少瓶啤酒。

线程的知识和运用先简单介绍到这,后续会增加实战的内容。

c 线程(平行世界)的更多相关文章

  1. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  2. [高并发]Java高并发编程系列开山篇--线程实现

    Java是最早开始有并发的语言之一,再过去传统多任务的模式下,人们发现很难解决一些更为复杂的问题,这个时候我们就有了并发. 引用 多线程比多任务更加有挑战.多线程是在同一个程序内部并行执行,因此会对相 ...

  3. 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)

    前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...

  4. Java 线程

    线程:线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程.线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不能拥有系统资源.它与父进程的其他线程共享该进程的所有资 ...

  5. C++实现线程安全的单例模式

    在某些应用环境下面,一个类只允许有一个实例,这就是著名的单例模式.单例模式分为懒汉模式,跟饿汉模式两种. 首先给出饿汉模式的实现 template <class T> class sing ...

  6. 记一次tomcat线程创建异常调优:unable to create new native thread

    测试在进行一次性能测试的时候发现并发300个请求时出现了下面的异常: HTTP Status 500 - Handler processing failed; nested exception is ...

  7. Android线程管理之ThreadLocal理解及应用场景

    前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...

  8. C#多线程之线程池篇3

    在上一篇C#多线程之线程池篇2中,我们主要学习了线程池和并行度以及如何实现取消选项的相关知识.在这一篇中,我们主要学习如何使用等待句柄和超时.使用计时器和使用BackgroundWorker组件的相关 ...

  9. C#多线程之线程池篇2

    在上一篇C#多线程之线程池篇1中,我们主要学习了如何在线程池中调用委托以及如何在线程池中执行异步操作,在这篇中,我们将学习线程池和并行度.实现取消选项的相关知识. 三.线程池和并行度 在这一小节中,我 ...

随机推荐

  1. ABP文档 - 后台作业和工作者

    文档目录 本节内容: 简介 后台作业 关于作业持久化 创建一个后台作业 在队列里添加一个新作业 默认的后台作业管理器 后台作业存储 配置 禁用作业执行 Hangfire 集成 后台工作者 创建一个后台 ...

  2. C# DateTime日期格式化

    在C#中DateTime是一个包含日期.时间的类型,此类型通过ToString()转换为字符串时,可根据传入给Tostring()的参数转换为多种字符串格式. 目录 1. 分类 2. 制式类型 3. ...

  3. 【初学python】使用python调用monkey测试

    目前公司主要开发安卓平台的APP,平时测试经常需要使用monkey测试,所以尝试了下用python调用monkey,代码如下: import os apk = {'j': 'com.***.test1 ...

  4. MySQL中interactive_timeout和wait_timeout的区别

    在用mysql客户端对数据库进行操作时,打开终端窗口,如果一段时间没有操作,再次操作时,常常会报如下错误: ERROR (HY000): Lost connection to MySQL server ...

  5. ls: 无法访问/usr/sbin/smartctl: 没有那个文件或目录

    环境:RHEL6.5 + Oracle 11.2.0.4 RAC 在安装RAC时,检查时缺少包 cvuqdisk-1.0.9-1,oracle提供脚本修复安装. 但在执行时报错: [root@orad ...

  6. 深入理解CSS六种颜色模式

    前面的话 赏心悦目的颜色搭配让人感到舒服,修改元素颜色的功能让人趋之若鹜.但颜色规划不当,会让网站用户无所适从.颜色从<font color="">发展至今,保留了很多 ...

  7. BI分析受阻?FineBI推出SPA螺旋式分析新功能!

    过去,企业级的数据分析通常会有这么几种场景,业务部门托信息部门分析数据,结果报表一出,唇枪舌剑争论你我高低,数据不准,指标不对.信息部门欠缺业务概念,业务部门不懂技术逻辑,数据分析之路,暂时搁浅. 后 ...

  8. BPM端到端流程解决方案分享

    一.需求分析 1.企业规模的不断发展.管理水平的不断提升,通常伴随着企业各业务板块管理分工更细.更专业,IT系统同样越来越多.越来越专 业化.不可避免的,部门墙和信息孤岛出现了,企业的流程被部门或者I ...

  9. gulp 自动添加版本号

    本文介绍利用 gulp-rev 和 gulp-rev-collector 进行版本管理 npm官网介绍使用后的效果如下: "/css/style.css" => " ...

  10. CentOS 7 修改主机名

    今天在阿里云上买了一个centos7的服务器,连接上以后,发现一个很长很长的主机名,看着让人很是不爽,就想着怎样将其改成一个有个性的名字. 这里我想说的是,在centos7 版本的linux系统上和c ...