c 线程(平行世界)

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

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

线程隆重登场

1. 如何创建线程

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

假设有两个函数

1 void * dose_do(void * a) {
2
3     for (int i = 0; i < 5; i++) {
4         sleep(1);
5         puts("does_do");
6     }
7
8     return NULL;
9 }
1 void * dose_not(void * a) {
2
3     for (int i = 0; i < 5; i++) {
4         sleep(1);
5         puts("does_not");
6     }
7
8     return NULL;
9 }

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

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

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

 1 // new pthread
 2     pthread_t t0;
 3     pthread_t t1;
 4
 5     if (pthread_create(&t0, NULL, dose_not, NULL) == -1) {
 6         error("无法创建线程t0");
 7     }
 8     if (pthread_create(&t1, NULL, dose_do, NULL) == -1) {
 9         error("无法创建线程t1");
10     }

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

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

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

1  void *result;
2     if (pthread_join(t0, &result) == -1) {
3         error("无法回收线程t0");
4     }
5     if (pthread_join(t1, &result) == -1) {
6         error("无法回收线程t1");
7     }

我们来看 全部代码

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <errno.h>
 6 #include <string.h>
 7
 8 // 错误处理函数
 9 void error(char *msg) {
10     fprintf(stderr, "Error: %s  %s", msg, strerror(errno));
11     exit(1);
12 }
13
14
15 void * dose_not(void * a) {
16
17     for (int i = 0; i < 5; i++) {
18         sleep(1);
19         puts("does_not");
20     }
21
22     return NULL;
23 }
24
25 void * dose_do(void * a) {
26
27     for (int i = 0; i < 5; i++) {
28         sleep(1);
29         puts("does_do");
30     }
31
32     return NULL;
33 }
34
35
36 int main(int argc, const char * argv[]) {
37
38     // new pthread
39     pthread_t t0;
40     pthread_t t1;
41
42     if (pthread_create(&t0, NULL, dose_not, NULL) == -1) {
43         error("无法创建线程t0");
44     }
45     if (pthread_create(&t1, NULL, dose_do, NULL) == -1) {
46         error("无法创建线程t1");
47     }
48
49     void *result;
50     if (pthread_join(t0, &result) == -1) {
51         error("无法回收线程t0");
52     }
53     if (pthread_join(t1, &result) == -1) {
54         error("无法回收线程t1");
55     }
56
57
58
59     return 0;
60 }

结果如下

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

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <errno.h>
 6 #include <string.h>
 7
 8 // 错误处理函数
 9 void error(char *msg) {
10     fprintf(stderr, "Error: %s  %s", msg, strerror(errno));
11     exit(1);
12 }
13
14
15 int beers = 2000000;
16
17 void * drink_lots(void * a) {
18
19
20     for (int i = 0; i < 100000; i++) {
21
22         beers = beers - 1;
23
24     }
25
26     printf("剩余 %i 瓶啤酒 \n",beers);
27
28     return NULL;
29 }
30
31
32 int main(int argc, const char * argv[]) {
33
34     // new pthread
35     pthread_t pthreads[20];
36
37     printf("%i 瓶啤酒 \n",beers);
38
39     for (int i = 0; i < 20; i++) {
40
41         if (pthread_create(&pthreads[i], NULL, drink_lots, NULL) == -1) {
42             error("无法创建线程");
43         }
44     }
45
46     void *result;
47
48     for (int i = 0; i < 20; i++) {
49
50         if (pthread_join(pthreads[i], &result) == -1) {
51             error("无法回收线程");
52         }
53     }
54
55
56
57     return 0;
58 }

运行结果

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

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

2. 用互斥锁保护线程

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

创建:

pthread_mutex_t beers_lock = PTHREAD_MUTEX_INITIALIZER;

加锁

pthread_mutex_lock(&beers_lock);

解锁

pthread_mutex_unlock(&beers_lock);

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

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <errno.h>
 6 #include <string.h>
 7
 8 // 错误处理函数
 9 void error(char *msg) {
10     fprintf(stderr, "Error: %s  %s", msg, strerror(errno));
11     exit(1);
12 }
13
14 pthread_mutex_t beers_lock = PTHREAD_MUTEX_INITIALIZER;
15
16 int beers = 2000000;
17
18 void * drink_lots(void * a) {
19
20
21     for (int i = 0; i < 100000; i++) {
22
23         pthread_mutex_lock(&beers_lock);
24         beers = beers - 1;
25         pthread_mutex_unlock(&beers_lock);
26     }
27
28     printf("剩余 %i 瓶啤酒 \n",beers);
29
30     return NULL;
31 }
32
33
34 int main(int argc, const char * argv[]) {
35
36     // new pthread
37     pthread_t pthreads[20];
38
39     printf("%i 瓶啤酒 \n",beers);
40
41     for (int i = 0; i < 20; i++) {
42
43         if (pthread_create(&pthreads[i], NULL, drink_lots, NULL) == -1) {
44             error("无法创建线程");
45         }
46     }
47
48     void *result;
49
50     for (int i = 0; i < 20; i++) {
51
52         if (pthread_join(pthreads[i], &result) == -1) {
53             error("无法回收线程");
54         }
55     }
56
57
58
59     return 0;
60 }

运行结果如下

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

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

时间: 2024-08-30 06:15:10

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

各科基础详实

一. Java基础部分 1. JAVA的基本数据类型有哪些 ?  String 是不是基本数据类型 ? 2. 一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 3. Java有没有goto? 7 4. 说说&和&&的区别. 7 5. 在JAVA中如何跳出当前的多重嵌套循环? 7 6. switch语句能否作用在byte上,能否作用在long上,能否作用在String上? 8 7. short s1 = 1; s1 = s1 + 1;有什么

来自平行世界的救赎

神级的拷问来了 为什么要说是救赎呢?先跟各位讨论个"死亡问题",如果你的女票或者你老婆问你,"我跟你妈落水了,你先救谁?" 哈哈,没错,就是这个来之中国的古老声音,这个拷问你内心的世纪难题!怕了没? 可以抛硬币,也可以找个渔网一次性捞起来,可是等等,在这紧急关头你真的有这么多时间? 此时的你肯定最想变成超人,或者修得绝世秘法"分身术",这样就不用做这道艰难的选择题了. 平行宇宙论告诉我们,这世界有无数个copy,你也有无数个copy,只要找另外一

Java四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor

介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? Java new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }).start(); 1 2 3 4 5 6 7 new Thread(new

201709018工作日记--线程状态的转换

先来张图: 线程在一定条件下,状态会发生变化: 1.新建状态(New):新创建了一个线程对象 2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权. 3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码. 4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行.直到线程进入就绪状态,才有机会转到运行状态.阻塞的情况分三种:   (一).等

POSIX 线程详解(经典必看)

总共三部分: 第一部分:POSIX 线程详解                                   Daniel Robbins ([email protected]), 总裁/CEO, Gentoo Technologies, Inc.  2000 年 7 月 01 日 第二部分:通用线程:POSIX 线程详解,第 2部分       Daniel Robbins ([email protected]), 总裁/CEO, Gentoo Technologies, Inc.  20

线程的控制和线程池

一.WaitHandle: ”.Net 中提供了一些线程间更自由通讯的工具,他们提供了通过"信号"进行通讯的机制 可以通过ManualResetEvent,AutoResetEvent(他是在开门并且一个 WaitOne 通过后自动关门)来进行线程间的通讯 waitOne:    等待开门 Set:           开门 Reset:       关门 static void Main(string[] args) { ManualResetEvent mre = new Manu

内存池、进程池、线程池

首先介绍一个概念"池化技术 ".池化技术 一言以蔽之就是:提前保存大量的资源,以备不时之需以及重复使用. 池化技术应用广泛,如内存池,线程池,连接池等等.内存池相关的内容,建议看看Apache.Nginx等开源web服务器的内存池实现. 起因:由于在实际应用当中,分配内存.创建进程.线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作.           因此,当程序中需要频繁的进行内存申请释放,进程.线程创建销毁等操作时,通常会使用内存池.进程池.

线程高级

例题,哲学家用餐: 在一张餐桌上坐着五个哲学家,但是却只有五根筷子,每个哲学家只有一根筷子,所以当一个哲学家要夹菜的时候需要用他旁边那个哲学家的筷子,被拿走筷子的哲学家则只能等待那个哲学家吃完放下筷子他才能夹菜. 示意图:  设计思路: 首先编写一个筷子类,每根筷子都是一个对象,这个类里有拿起.放下两个方法:当一个哲学家线程来调用拿起方法时,下一个哲学家线程就要进入等待状态,然后这个哲学家线程调用放下方法后,就激活那个等待中的哲学家线程,以此循环,轮流使用筷子. 代码示例:  接着设计哲学家类,

缓冲池,线程池,连接池

SSH:[email protected]:unbelievableme/object-pool.git   HTTPS:https://github.com/unbelievableme/object-pool.git 缓冲池 设计要点:包含三个队列:空缓冲队列(emq),装满输入数据的输入的队列(inq),装满输出数据的输出队列(outq),输入程序包括收容输入(hin),提取输入(sin),输出程序包括收容输出(hout)和提取输出(sout). 注意点:输入程序和输出程序会对缓冲区并发访