zoukankan      html  css  js  c++  java
  • 【C编程基础】多线程编程

    基础知识

    1.基本概念

    (1)线程,即轻量级进程(LWP:LightWeight Process),是程序执行流的最小单元。 线程是进程中的一个实体,是被系统独立调度和分派的基本单位。

    (2)线程同步,就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。这里的同步千万不要理解成那个同时进行,应是指协同、协助、互相配合。

    (3)线程互斥,是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。

    2.三种基本状态

    就绪状态,指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;

    运行状态,指线程占有处理机正在运行;

    阻塞状态,指线程在等待一个事件(如信号量),逻辑上不可执行。

    3.进程和线程的关系

    简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

    进程和线程的主要差别在于它们是不同的操作系统资源管理方式。

    进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。

    线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

    对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

    4.线程同步互斥的4种方式

    (1)临界区(Critical Section):适合一个进程内的多线程访问公共区域或代码段时使用

    (2)互斥量 (Mutex):适合不同进程内多线程访问公共区域或代码段时使用,与临界区相似。

    (3)事件(Event):通过线程间触发事件实现同步互斥

    (4)信号量(Semaphore):与临界区和互斥量不同,可以实现多个线程同时访问公共区域数据,原理与操作系统中PV操作类似,先设置一个访问公共区域的线程最大连接数,每有一个线程访问共享区资源数就减一,直到资源数小于等于零。

    #include <pthread.h>多线程函数

    线程按照其调度者可以分为用户级线程和核心级线程两种 。用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不需要特定的内核支持;

    我们常用基本就是用户级线程,总结一下POSIX提供的用户级线程接口。

    基本线程操作相关的函数: 
    1.线程的建立结束 

    函数说明
    pthread_create() 创建线程开始运行相关线程函数,运行结束则线程退出
    pthread_eixt() 因为exit()是用来结束进程的,所以则需要使用特定结束线程的函数
    pthread_join() 挂起当前线程,用于阻塞式地等待线程结束,如果线程已结束则立即返回,0=成功
    pthread_cancel() 发送终止信号给thread线程,成功返回0,但是成功并不意味着thread会终止
    pthread_testcancel() 在不包含取消点,但是又需要取消点的地方创建一个取消点,以便在一个没有包含取消点的执行代码线程中响应取消请求.
    pthread_setcancelstate() 设置本线程对Cancel信号的反应
    pthread_setcanceltype() 设置取消状态 继续运行至下一个取消点再退出或者是立即执行取消动作
    pthread_setcancel() 设置取消状态

    2.线程的互斥和同步 

    函数说明
    pthread_mutex_init() 互斥锁的初始化
    pthread_mutex_lock() 锁定互斥锁,如果尝试锁定已经被上锁的互斥锁则阻塞至可用为止
    pthread_mutex_trylock() 非阻塞的锁定互斥锁
    pthread_mutex_unlock() 释放互斥锁
    pthread_mutex_destory() 互斥锁销毁函数

    3.使用信号量控制线程 (默认无名信号量)

    函数说明
    sem_init(sem) 初始化一个定位在sem的匿名信号量
    sem_wait() 把信号量减1操作,如果信号量的当前值为0则进入阻塞,为原子操作
    sem_trywait() 如果信号量的当前值为0则返回错误而不是阻塞调用(errno=EAGAIN),其实是sem_wait()的非阻塞版本
    sem_post() 给信号量的值加1,它是一个“原子操作”,即同时对同一个信号量做加1,操作的两个线程是不会冲突的
    sem_getvalue(sval) 把sem指向的信号量当前值放置在sval指向的整数上
    sem_destory(sem) 销毁由sem指向的匿名信号量

    4.线程的基本属性配置

    函数说明
    pthread_attr_init() 初始化配置一个线程对象的属性,需要用pthread_attr_destroy函数去除已有属性
    pthread_attr_setscope() 设置线程属性
    pthread_attr_setschedparam() 设置线程优先级
    pthread_attr_getschedparam() 获取线程优先级

     注意:使用线程,编译时需要引用软件库lpthread,如下:

    gcc -o thread thread.c -lpthread
    /* thread.c */
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    #define THREAD_NUMBER       3                 /*线程数*/
    #define REPEAT_NUMBER       5                 /*每个线程中的小任务数*/
    #define DELAY_TIME_LEVELS  10.0             /*小任务之间的最大时间间隔*/
    //
    void * thrd_func(void *arg) { 
        /* 线程函数例程 */
        int thrd_num = (long)arg;
        int delay_time = 0;
        int count = 0;
        printf("Thread %d is starting
    ", thrd_num);
        for (count = 0; count < REPEAT_NUMBER; count++) {
            delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1;
            sleep(delay_time);
            printf("	Thread %d: job %d delay = %d
    ",thrd_num, count, delay_time);
        }
    
        printf("Thread %d finished
    ", thrd_num);
        pthread_exit(NULL);
    }
    
    int main(void) {
         pthread_t thread[THREAD_NUMBER];
         int no = 0, res;
         void * thrd_ret;
         srand(time(NULL));    
         for (no = 0; no < THREAD_NUMBER; no++) {
              /* 创建多线程 */
              res = pthread_create(&thread[no], NULL, thrd_func, (void*) (long) no);
              if (res != 0) {
                   printf("Create thread %d failed
    ", no);
                   exit(res);
              }
         }
    
         printf("Create treads success
     Waiting for threads to finish...
    ");
         for (no = 0; no < THREAD_NUMBER; no++) {
              /* 等待线程结束 */
              res = pthread_join(thread[no], &thrd_ret);
              if (!res) {
                printf("Thread %d joined
    ", no);
              } else {
                printf("Thread %d join failed
    ", no);
              }
         }
         return 0;        
    }
    
    
    例程中循环3次建立3条线程,并且使用pthread_join函数依次等待线程结束; 
    线程中使用rand()获取随机值随机休眠5次,随意会出现后执行的线程先执行完成; 
    
    可以看到,线程1先于线程0执行,但是pthread_join的调用时间顺序,先等待线程0执行; 
    由于线程1已经早结束,所以线程0被pthread_join等到的时候,线程1已结束,就在等待到线程1时,直接返回;
    
    
    执行结果如下:
    Create treads success
     Waiting for threads to finish...
    Thread 0 is starting
    Thread 1 is starting
    Thread 2 is starting
            Thread 0: job 0 delay = 3
            Thread 1: job 0 delay = 9
            Thread 2: job 0 delay = 10
            Thread 0: job 1 delay = 9
            Thread 1: job 1 delay = 4
            Thread 0: job 2 delay = 1
            Thread 2: job 1 delay = 7
            Thread 0: job 3 delay = 8
            Thread 1: job 2 delay = 9
            Thread 1: job 3 delay = 1
            Thread 2: job 2 delay = 9
            Thread 1: job 4 delay = 4
    Thread 1 finished
            Thread 0: job 4 delay = 10
    Thread 0 finished
    Thread 0 joined
    Thread 1 joined
            Thread 2: job 3 delay = 8
            Thread 2: job 4 delay = 4
    Thread 2 finished
    Thread 2 joined
    基本线程建立示例
    gcc -o thread_mutex thread_mutex.c -lpthread
    /*thread_mutex.c*/
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
     
    #define THREAD_NUMBER        3            /* 线程数 */
    #define REPEAT_NUMBER        3            /* 每个线程的小任务数 */
    #define DELAY_TIME_LEVELS 10.0         /*小任务之间的最大时间间隔*/
    pthread_mutex_t mutex;
     
    void *thrd_func(void *arg) {
         int thrd_num = (int)(long) arg;
         int delay_time = 0, count = 0;
         int res;
         /* 互斥锁上锁 */
         res = pthread_mutex_lock(&mutex);
         if (res) {
              printf("Thread %d lock failed
    ", thrd_num);
              pthread_exit(NULL);
         }
         printf("Thread %d lock 
    ", thrd_num);
         printf("Thread %d is starting
    ", thrd_num);
         for (count = 0; count < REPEAT_NUMBER; count++) {          
             delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1;
             sleep(delay_time);
             printf("	Thread %d: job %d delay = %d
    ", 
                                          thrd_num, count, delay_time);
         }
         printf("Thread %d finished
    ", thrd_num);
         res = pthread_mutex_unlock(&mutex);
         if (res) {
              printf("Thread %d unlock failed
    ", thrd_num);
              pthread_exit(NULL);
         }
         printf("Thread %d unlock
    ", thrd_num);
    
         pthread_exit(NULL);
    }
     
    int main(void) {
         pthread_t thread[THREAD_NUMBER];
         int no = 0, res;
         void * thrd_ret;
     
         srand(time(NULL));
         /* 互斥锁初始化 */
         pthread_mutex_init(&mutex, NULL);
         for (no = 0; no < THREAD_NUMBER; no++) {
              res = pthread_create(&thread[no], NULL, thrd_func, (void*)(long)no);
              if (res != 0) {
                  printf("Create thread %d failed
    ", no);
                  exit(res);
              }
         }     
         printf("Create treads success
     Waiting for threads to finish...
    ");
         for (no = 0; no < THREAD_NUMBER; no++) {
              res = pthread_join(thread[no], &thrd_ret);
              if (!res) {
                    printf("Thread %d joined
    ", no);
              } else  {
                  printf("Thread %d join failed
    ", no);
              }
         }   
         /****互斥锁解锁***/
         pthread_mutex_destroy(&mutex);          
         return 0;        
    }
    
    
    添加同步锁pthread_mutex_t
    在线程中加入,于是程序在执行线程程序时; 
    调用pthread_mutex_lock上锁,发现上锁时候后进入等待,等待锁再次释放后重新上锁; 
    所以线程程序加载到队列中等待,等待成功上锁后继续执行程序代码;
    
    运行结果:
    Create treads success
     Waiting for threads to finish...
    Thread 2 lock 
    Thread 2 is starting
            Thread 2: job 0 delay = 7
            Thread 2: job 1 delay = 2
            Thread 2: job 2 delay = 1
    Thread 2 finished
    Thread 2 unlock
    Thread 1 lock 
    Thread 1 is starting
            Thread 1: job 0 delay = 2
            Thread 1: job 1 delay = 6
            Thread 1: job 2 delay = 8
    Thread 1 finished
    Thread 1 unlock
    Thread 0 lock 
    Thread 0 is starting
            Thread 0: job 0 delay = 7
            Thread 0: job 1 delay = 1
            Thread 0: job 2 delay = 8
    Thread 0 finished
    Thread 0 unlock
    Thread 0 joined
    Thread 1 joined
    Thread 2 joined
    实现线程同步(互斥量)

    1.创建线程

    int pthread_create(pthread_t *restrict_ptid,const pthread_attr_t *restrict_attr,void *(*start_routine)(void*), void *restrict_arg);

    (1)参数说明:

    • ptid是一个pthread_t *类型的指针,pthread_t是类似pid_t的数据结构,表示线程ID;
    • attr指明线程创建属性,如果为NULL就使用系统默认属性;
    • start_routine是线程的主函数,它的参数是void *类型的指针,返回值也是void *类型的指针;
    • arg是线程创建者传递给新建线程的参数,也就是start_routine的参数,如果需要向start_routine函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为restrict_arg的参数传入。

    (2)返回值:

    若线程创建成功,则返回0。若线程创建失败,则返回出错编号

    (3)注意事项:

    线程创建者和新建线程之间没有fork()调用那样的父子关系,它们是对等关系。调用pthread_create()创建线程后,线程创建者和新建线程哪个先运行是不确定的,特别是在多处理机器上。

    2.终止线程

    void pthread_exit(void *value_ptr);

    (1)参数说明:value_ptr作为线程的返回值被调用pthread_join的线程使用。

    (2)注意事项:由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放,但是可以用pthread_join()函数来同步并释放资源。


    3.pthread_self():该函数返回调用线程的ID.这个数值与调用 pthread_create 创建本线程时使用的*thread 参数返回的值是一样的。
      注意:Thread IDs 仅仅保证在一个进程内部的唯一性。当一个结束了的线程 joined(使用join等待一个线程结束)之后, 或者一个detached 状态的线程被结束 thread ID可能会被重新使用。 pthread_self()返回的线程ID与 调用 gettid()得到的内核线程ID是不一样的。


    4.pthread_equal():比较线程ID,线程ID的大小没有意义。
      引入原因:在线程中,线程ID的类型是pthread_t类型,由于在Linux下线程采用POSIX标准,所以,在不同的系统下,pthread_t的类型是不同的,比如在ubuntn下,是unsigned long类型,而在solaris系统中,是unsigned int类型。而在FreeBSD上才用的是结构题指针。 所以不能直接使用==判读,而应该使用pthread_equal来判断。


    3.取消线程
    int pthread_cancel(pthread_t thread);
      注意:若是在整个程序退出时,要终止各个线程,应该在成功发送 CANCEL指令后,使用 pthread_join函数,等待指定的线程已经完全退出以后,再继续执行;否则,很容易产生 “段错误”。

    4.连接线程(阻塞)
    int pthread_join(pthread_t thread, void **value_ptr);
      等待线程thread结束,并设置*value_ptr为thread的返回值。pthread_join阻塞调用者,一直到线程thread结束为止。当函数返回时,被等待线程的资源被收回。如果进程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
      线程终止有一下几种方法:
      1.从主函数返回;
      2.自己调用pthread_exit();
      3.其他线程调用pthread_cancel();
      4.线程所属的进程中任何线程调用exit()导致所有线程结束。

    5.分离线程
    int pthread_detach(pthread_t thread);
      分离线程的语意是,线程thread结束后系统可以回收它的私有数据。
      注释:pthread有两种状态joinable状态和unjoinable状态 一个线程默认的状态是joinable,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己,如:pthread_detach(pthread_self())或者父线程调用pthread_detach(thread_id)结束相应子进程。

    参考文档:

    多线程函数的使用总结

     pThreads线程(一) 基本API

    线程同步互斥的4种方式

    pThreads线程(一) 基本API

  • 相关阅读:
    hbase 导入导出、hbase shell 基本命令。
    一道java基础面试题
    sqoop2报错
    测试往博客上放音乐
    java 压缩文件
    java 提取目录下所有子目录的文件到指定位置
    3.Git的常用操作
    2.Git客户端安装
    1.Git的由来
    『Spring』IOC创建对象的方式
  • 原文地址:https://www.cnblogs.com/badboy200800/p/10239310.html
Copyright © 2011-2022 走看看