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

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

线程隆重登场

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. 数学思想:为何我们把 x²读作x平方

    要弄清楚这个问题,我们得先认识一个人.古希腊大数学家 欧多克索斯,其在整个古代仅次于阿基米德,是一位天文学家.医生.几何学家.立法家和地理学家. 为何我们把 x²读作x平方呢? 古希腊时代,越来越多的 ...

  2. Linux下服务器端开发流程及相关工具介绍(C++)

    去年刚毕业来公司后,做为新人,发现很多东西都没有文档,各种工具和地址都是口口相传的,而且很多时候都是不知道有哪些工具可以使用,所以当时就想把自己接触到的这些东西记录下来,为后来者提供参考,相当于一个路 ...

  3. C++ std::set

    std::set template < class T, // set::key_type/value_type class Compare = less<T>, // set::k ...

  4. 移动端1px边框

    问题:移动端1px边框,看起来总是2倍的边框大小,为了解决这个问题试用过很多方法,用图片,用js判断dpr等,都不太满意, 最后找到一个还算好用的方法:伪类 + transform 原理是把原先元素的 ...

  5. 使用Java原生代理实现AOP

    ### 本文由博主柒.原创,转载请注明出处 ### 完整源码下载地址 [https://github.com/MatrixSeven/JavaAOP](https://github.com/Matri ...

  6. MJRefresh 源码解读 + 使用

    MJRefresh这个刷新控件是一款非常好用的框架,我们在使用一个框架的同时,最好能了解下它的实现原理,不管是根据业务要求在原有的基础上修改代码,还是其他的目的,弄明白作者的思路和代码风格,会受益匪浅 ...

  7. 免费高效实用的.NET操作Excel组件NPOI(.NET组件介绍之六)

    很多的软件项目几乎都包含着对文档的操作,前面已经介绍过两款操作文档的组件,现在介绍一款文档操作的组件NPOI. NPOI可以生成没有安装在您的服务器上的Microsoft Office套件的Excel ...

  8. 小兔Java教程 - 三分钟学会Java文件上传

    今天群里正好有人问起了Java文件上传的事情,本来这是Java里面的知识点,而我目前最主要的精力还是放在了JS的部分.不过反正也不麻烦,我就专门开一贴来聊聊Java文件上传的基本实现方法吧. 话不多说 ...

  9. ASP.NET Core 中文文档 第四章 MVC(3.9)视图组件

    作者: Rick Anderson 翻译: 娄宇(Lyrics) 校对: 高嵩 章节: 介绍视图组件 创建视图组件 调用视图组件 演练:创建一个简单的视图组件 附加的资源 查看或下载示例代码 介绍视图 ...

  10. javascript动画系列第一篇——模拟拖拽

    × 目录 [1]原理介绍 [2]代码实现 [3]代码优化[4]拖拽冲突[5]IE兼容 前面的话 从本文开始,介绍javascript动画系列.javascript本身是具有原生拖放功能的,但是由于兼容 ...