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

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

线程隆重登场

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. Golang, 以17个简短代码片段,切底弄懂 channel 基础

    (原创出处为本博客:http://www.cnblogs.com/linguanh/) 前序: 因为打算自己搞个基于Golang的IM服务器,所以复习了下之前一直没怎么使用的协程.管道等高并发编程知识 ...

  2. 百度推出新技术 MIP,网页加载更快,广告呢?

    我们在2016年年初推出了MIP,帮助移动页面加速(原理).内测数据表明,MIP页面在1s内加载完成.现在已经有十多家网站加入MIP项目,有更多的网站正在加入中.在我们收到的反馈中,大部分都提到了广告 ...

  3. TDD在Unity3D游戏项目开发中的实践

    0x00 前言 关于TDD测试驱动开发的文章已经有很多了,但是在游戏开发尤其是使用Unity3D开发游戏时,却听不到特别多关于TDD的声音.那么本文就来简单聊一聊TDD如何在U3D项目中使用以及如何使 ...

  4. OpenGL超级宝典笔记----框架搭建

    自从工作后,总是或多或少的会接触到客户端3d图形渲染,正好自己对于3d图形的渲染也很感兴趣,所以最近打算从学习OpenGL的图形API出发,进而了解3d图形的渲染技术.到网上查了一些资料,OpenGL ...

  5. 用FSM一键制作逐帧动画雪碧图 Vue2 + webpack

    因为工作需要要将五六十张逐帧图拼成雪碧图,网上想找到一件制作工具半天没有找到,就自己用canvas写了一个. 写成之后就再没有什么机会使用了,因此希望有人使用的时候如果遇到bug了能及时反馈给我. 最 ...

  6. HTML5轻松实现搜索框提示文字点击消失---及placeholder颜色的设置

    在做搜索框的时候无意间发现html5的input里有个placeholder属性能轻松实现提示文字点击消失功能,之前还傻傻的在用js来实现类似功能... 示例 <form action=&quo ...

  7. 通过AngularJS实现前端与后台的数据对接(一)——预备工作篇

    最近,笔者在做一个项目:使用AngularJS,从而实现前端与后台的数据对接.笔者这是第一次做前端与后台的数据对接的工作,因此遇到了许多问题.笔者在这些问题中,总结了一些如何实现前端与后台的数据对接的 ...

  8. android_m2repository_rxx.zip下载地址以及MD5

    地址 MD5 https://dl-ssl.google.com/android/repository/android_m2repository_r08.zip 8C8EC4C731B7F55E646 ...

  9. 机器学习之sklearn——EM

    GMM计算更新∑k时,转置符号T应该放在倒数第二项(这样计算出来结果才是一个协方差矩阵) from sklearn.mixture import GMM    GMM中score_samples函数第 ...

  10. CSharpGL(10)两个纹理叠加

    CSharpGL(10)两个纹理叠加 本文很简单,只说明如何用shader实现叠加两个纹理的效果. 另外,最近CSharpGL对渲染框架做了修改,清理一些别扭的内容(DoRender()前后的事件都去 ...