zoukankan      html  css  js  c++  java
  • 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 }

    运行结果如下

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

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

  • 相关阅读:
    F版本SpringCloud1—大白话为啥要有微服务?啥是微服务?SpringCloud为什么有那么多组件?
    Java已五年1—二本物理到前端实习生到Java程序员「回忆贴」
    SpringBoot图文教程17—上手就会 RestTemplate 使用指南「Get Post」「设置请求头」
    SpringBoot图文教程15—项目异常怎么办?「跳转404错误页面」「全局异常捕获」
    SpringBoot图文教程14—SpringBoot集成EasyExcel「上」
    SpringBoot图文教程12—SpringData Jpa的基本使用
    SpringBoot图文教程11—从此不写mapper文件「SpringBoot集成MybatisPlus」
    SpringBoot图文教程10—模板导出|百万数据Excel导出|图片导出「easypoi」
    SpringBoot图文教程9—SpringBoot 导入导出 Excel 「Apache Poi」
    SpringBoot图文教程8 — SpringBoot集成MBG「代码生成器」
  • 原文地址:https://www.cnblogs.com/machao/p/5673981.html
Copyright © 2011-2022 走看看