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 瓶啤酒 
",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 瓶啤酒 
",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 瓶啤酒 
",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 瓶啤酒 
",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 }

运行结果如下

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

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

原文地址:https://www.cnblogs.com/machao/p/5673981.html