我们已经知道如何使用进程来做一些事情了,然而 它并不是在什么地方都是最适合的。
我们看看进程的缺点是什么:
线程隆重登场
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 }
运行结果如下
每个线程中循环结束后才会打印结果,也就是说当循环完之后打印的结果就是那个时间点还剩多少瓶啤酒。
线程的知识和运用先简单介绍到这,后续会增加实战的内容。