zoukankan      html  css  js  c++  java
  • Linux基础(15)多线程编程

    Linux的内核中没有thread的概念,线程是第三方库libpthread实现的, 和vfork(轻量级进程,只有部分copy)有点像(进程的创建fork会完全copy主进程资源 ,而线程会共享资源,子线程创建新资源时其作用域只在当前子线程,而子线程非新新创建的资源会和创建前的主线程共享这些资源) , 线程和进程的创建在内核里都是系统调用copy_process ,但是他们的实现不同, 进程会调用fork()的copy_process(全拷贝) ,而线程调用的是thread的copy_process(第三方库实现的,只拷贝部分) , copy_process是一个虚函数

    小知识: Linux 有个内存文件系统挂载在内存里(shell:cd /proc)用作调试使用, 里面有很多数,这些是进程或线程的ID, 这些文件里有他们的调试信息,比如 ps -ef 所展示的信息

        shell:cd空格/work/pthread , 在这下面使用 pmap(进程映射工具)把进程信息保存(pmap [ThreadNum] > a.txt)

        pmap -x  [ThreadNum] >a.txt

        pmap -x  [ThreadNum] >b.txt

        可以使用shell:diff a.txt b.txt  比对保存的两个进程或线程信息 有何区别

     线程的概念  进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位

           (进程只是系统分配的资源而执行的是线程(在没有创建子线程时进程默认有一个线程执行,创建子线程后默认的线程作为主线程))

      什么是堆 ,什么是栈 ,他们在哪  https://www.cnblogs.com/valor-xh/p/6535421.html

      线程不代表资源,是程序的最小执行单元-----每一个线程都有一个栈 ,当线程结束时栈会被回收

      进程代表资源,是程序调度的最小单元------创建的子线程共享主进程的一切资源, 而子进程新创建的资源不与主线程和其他线程共享

      多线程是并发执行的,但也是有数量限制的,过了时间片要等待下一次的CPU调度 线程和进程除了资源的区别其他和进程一样

      多线程的并行其实是伪并行,CPU的调度是有轮转的,比如A线程被CPU调度后,CPU寄存器等是被A所占用的,而A过了时间片后其状态和信息(寄存器里的值)会被保存在tss段, 而B线程会的状态和信息会被恢复到CPU

      

    线程的管理

      gcc -g -o xxx xxx.c -lpthread

      pthread_self()     获得线程tid(由线程库维护的 ,其ID空间的各个进程独立的, ,因此每个进程的线程可能会相同)

      syscall(SYS_gettid),  获取内核中的线程ID(内核中没有线程的概念只有轻量级进程 ,SYS_gettid则是获取内核中这个轻量级进程的ID 是唯一的)

       syscall: http://blog.chinaunix.net/uid-28458801-id-4630215.html

      线程的创建

        int pthread_create(pthread_t *thread , const pthread_attr_t *attr , void(start_routine)(void*) , void *arg );

          pthread_t *thread     线程的id 放一个指针,线程创建完后作为返回值返回给 传入的指针 返回指向线程的首地址

          const pthread_attr_t *attr  NULL 线程的属性

           void(start_routine)(void*)  线程的执行函数

           void *arg         传递给线程函数的参数

      线程的退出与等待

        void pthread_exit(void *retval);  自行退出当前线程  retval是一个返回值,可以NULL

        其他线程调用 pthread_cancel

        线程执行完后也是自动退出 , 创建线程的进程退出后线程也会退出

        其中一个线程执行了exec类函数,因为会替换当前进程的所有的地址空间 线程也会退出

        int pthread_join(pthread_t thread, void **retval); 等待指定线程退出后,会收到指定线程退出时返回的值 ,也可以NULL不接收

    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    void *helloworld(char *argc);
    int main(int argc,int argv[])
    {
        int error;
        int *temptr;
        
        pthread_t thread_id;
        
        pthread_create(&thread_id,NULL,(void *)*helloworld,"helloworld");    //创建子线程
        printf("*p=%x,p=%x
    ",*helloworld,helloworld);
        if(error=pthread_join(thread_id,(void **)&temptr))                    //等待子线程退出,并接受返回的堆指针
        {
            perror("pthread_join");
            exit(EXIT_FAILURE);    
        }
        printf("temp=%x,*temp=%c
    ",temptr,*temptr);
        *temptr='d';                                                        //使用子线程创建的堆
        printf("%c
    ",*temptr);    
        free(temptr);                                                        //释放
        return 0;
    }
    
    void *helloworld(char *argc)
    {
        int *p;
        p=(int *)malloc(10*sizeof(int));                                //子线程创建了一个堆
        printf("the message is %s
    ",argc);    
        printf("the child id is %u
    ",pthread_self());
        memset(p,'c',10);
        printf("p=%x
    ",p);
        pthread_exit(p);                                                //把堆的指针返回出去
        //return 0;
    }
    thread_exit_ join

      线程的取消  

        线程能否被取消要看两点:

          线程是否具有可取消属性---默认可以被取消

          线程如果设置为到可取消点才能被取消时,线程被取消是不会被立刻取消

        int pthread_cancel(pthread_t thread);

        线程的取消状态属性

          int pthread_setcancelstate(int state, int *oldstate);

            state : PTHREAD_CANCEL_ENABLE 可取消属性

               PTHREAD_CANCEL_DISABLE---->不可取消

        线程的取消类型属性  A线程收到了B线程的取消请求时请求时 ,要根据取消类型判断是立即取消飞行合适再取消(取决于系统)

           int pthread_setcanceltype(int type, int *oldtype);      

            type: 立刻被取消 PTHREAD_CANCEL_ASYNCHRONOUS

                只有到达一定取消点,才会取消 PTHREAD_CANCEL_DEFERRED

      线程的私有数据

        TSD私有数据,同名但是不同内存地址的私有数据结构, 比如fork()后子进程继承了父进程的一切包括全局变量的值,但是在子进程修改了这个全局变量却不影响父进程的全局变量的值, 父进程的这个全局变量也不影响子进程的值, 两者不在同一内存地址互不影响

        https://blog.csdn.net/mengxingyuanlove/article/details/50802246

        int pthread_key_create(pthread_key_t key, void (destructor)(void*)); 创建

        int pthread_key_delete(pthread_key_t key);           删除

        void *pthread_getspecific(pthread_key_t key);          获取

        int pthread_setspecific(pthread_key_t key, const void *value);   设置

    //this is the test code for pthread_key 
    
    #include <stdio.h> 
    #include <pthread.h> 
    
    pthread_key_t key;                                             //线程私有类型的变量
    void echomsg(void *t) 
    { 
        printf("destructor excuted in thread %u,param=%u
    ",pthread_self(),((int *)t)); 
    } 
    
    void * child1(void *arg) 
    { 
        int i=10;
        int tid=pthread_self(); 
        printf("
    set key value %d in thread %u
    ",i,tid); 
        pthread_setspecific(key,&i);                                 //要使用特定的设置函数才能设置当前线程私有的变量
        printf("thread one sleep 2 until thread two finish
    ");
        sleep(2); 
        printf("
    thread %u returns %d,add is %u
    ",tid,*((int *)pthread_getspecific(key)),(int *)pthread_getspecific(key)); 
    } 
    
    void * child2(void *arg) 
    { 
        int temp=20;
        int tid=pthread_self(); 
        printf("
    set key value %d in thread %u
    ",temp,tid); 
        pthread_setspecific(key,&temp); 
        sleep(1); 
        printf("thread %u returns %d,add is %u
    ",tid,*((int *)pthread_getspecific(key)),(int *)pthread_getspecific(key)); 
    } 
    
    int main(void) 
    { 
        pthread_t tid1,tid2; 
        pthread_key_create(&key,echomsg);                 //创建线程私有的变量,每个线程都可以继承这个变量,但是内存地址不同而互不影响 ,在线程结束后会调用echomsg()
        pthread_create(&tid1,NULL,(void *)child1,NULL); 
        pthread_create(&tid2,NULL,(void *)child2,NULL); 
        pthread_join(tid1,NULL);
        pthread_join(tid2,NULL);
        pthread_key_delete(key);                         //删除线程私有变量
        return 0; 
    } 
    pthread_key_test

    线程占有的都是不共享的,其中包括:栈、寄存器、状态、程序计数器

    线程间共享的有:堆,全局变量,静态变量;


                线程的互斥的方式与更精细的互斥: 互斥锁,条件变量 , 读写锁

        互斥锁的通信机制

          pthread_mutex_t                  互斥锁对象

          int pthread_mutex_destroy(pthread_mutex_t *mutex);  销毁对象

          int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);    初始化对象

          int pthread_mutex_lock(pthread_mutex_t *mutex);    上锁

          int pthread_mutex_trylock(pthread_mutex_t *mutex);   非阻塞函数,无论是否上锁都直接返回

          int pthread_mutex_unlock(pthread_mutex_t *mutex);   解锁

           无论读写共享内存都要先上锁,读完写完都要解锁

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <semaphore.h>
    #include <string.h>
    
    void *thread_function(void *arg);
    
    pthread_mutex_t work_mutex;             //互斥锁
    
    #define WORK_SIZE 1024
    char work_area[WORK_SIZE];                //共享内存区域
    int time_to_exit = 0;                    //是否退出的信号
    
    int main(int argc,char *argv[]) 
    {
        int res;
        pthread_t a_thread;
        void *thread_result;
        res = pthread_mutex_init(&work_mutex, NULL);         //初始化并创建锁对象
        if (res != 0) 
        {
            perror("Mutex initialization failed");
            exit(EXIT_FAILURE);
            }
            res = pthread_create(&a_thread, NULL, thread_function, NULL);        //创建线程
        if (res != 0) 
        {
            perror("Thread creation failed");
            exit(EXIT_FAILURE);
        }
        pthread_mutex_lock(&work_mutex);                                        //上锁
        printf("Input some text. Enter 'end' to finish
    ");
        while(!time_to_exit)                                                     //判断是否输入了end,如果为1则退出循环写                
        {
            fgets(work_area, WORK_SIZE, stdin);        //get a string from stdin    //往共享区域里写
            pthread_mutex_unlock(&work_mutex);        //unlock the mutex            //解锁
            while(1) 
            {
                pthread_mutex_lock(&work_mutex);                                //上锁
                if (work_area[0] != '')                                         //如果不是说明有字符还没读
                {
                    pthread_mutex_unlock(&work_mutex);                            //解锁
                    sleep(1);                                                    //休眠1秒让子线程读取完整
                }
                else 
                {
                    break;                                                        //如果是 说明没有数据读了,则循环再次输入
                }
            }
        }
        pthread_mutex_unlock(&work_mutex);                                        //到了这里说明用户输入了end,解锁
        printf("
    Waiting for thread to finish...
    ");
        res = pthread_join(a_thread, &thread_result);                            //阻塞等待子线程完成操作
        if (res != 0) 
        {
            perror("Thread join failed");
            exit(EXIT_FAILURE);
        }
        printf("Thread joined
    ");
        pthread_mutex_destroy(&work_mutex);                                        //删除锁
        exit(EXIT_SUCCESS);                                                        //退出进程
    }
    
    void *thread_function(void *arg) 
    {
        sleep(1);                                                    //休眠1秒让主线程先上锁
        pthread_mutex_lock(&work_mutex);                            //如果目标共享区域已上锁则自动休眠知道解锁为止
        while(strncmp("end", work_area, 3) != 0)                     //上锁后判断共享区域是否输入end,如果有end则不进入循环
        {
            printf("You input %d characters
    ", strlen(work_area) -1);
            printf("the characters is %s",work_area);                //打印共享区域的内容
            work_area[0] = '';                                    //读完内容后那内容置0
            pthread_mutex_unlock(&work_mutex);                        //解锁
            sleep(1);                                                //休眠1秒再次让主线程上锁
            pthread_mutex_lock(&work_mutex);                        //上锁
            while (work_area[0] == '' )                             //判断是否有数据,一直循环直到有数据来
            {
                pthread_mutex_unlock(&work_mutex);                    //如果什么都没有解锁
                sleep(1);                                            //休眠1秒
                pthread_mutex_lock(&work_mutex);                    //上锁
            }
        }
        time_to_exit = 1;                                            //发出用户输入end想结束对话的信号状态
        work_area[0] = '';                        
        pthread_mutex_unlock(&work_mutex);
        pthread_exit(0);
    }
    pthread_mutex

        条件变量通信机制    

    pthread_mutex_t work_mutex 
    int i=3;
    int j=7;
    ___________________________________________________ pthread_A()
    {
      pthread_mutex_lock
      i++;
      j--;
      pthread_unlock
    }
    ______________________________________________________
    pthread_B()
    {
      pthread_mutex_lock
      if(i==j)
      {
        do_something();
      }
      pthread_unlock
    }
    _________________________________________________________
    这样的互斥锁虽然安全但是有BUG,因为线程也是和进程一样受限于CPU的随机调度 ,如果 i==5==j 的时候因为CPU的随机调度被A线程再次抢占了就导致 i==6 4==j ,
    B线程会因此不满足出发条件, 且pthread_B只会在 i==j 才会执行,但是会不断的消耗CPU去判断 i是否等于j ,引入条件变量就可以解决这两个问题了
                              条件变量必须配合互斥锁使用
    __________________________________________________________
    pthread_A()
    {
      pthread_mutex_lock
      i++;
      j--; 
      if(i==j)
      {
        pthread_cond_signal(); 满足条件后发出信号
      }
      pthread_unlock
    }
    ______________________________________________________
    pthread_B()
    {
      pthread_mutex_lock
      pthread_cond_wait();    当前函数会阻塞(自动解锁,被唤醒后自动上锁),直到A线程发出信号才会继续执行
      do_something();
      pthread_unlock
    }

          条件变量对象: pthread_cond_t condtion

            int pthread_cond_destroy(pthread_cond_t *cond);  销毁条件变量的对象

            int pthread_cond_init(pthread_cond_t * cond,const pthread_condattr_t * attr);  初始化条件变量

            pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

          条件变量等待: timedwait()超过设定的时间限制会立刻返回  wait()等待,直到发出满足条件的信号为止

            int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);

            int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

            wait()会自动解锁mutex_lock等待其他线程触发条件激活wait

            wait的实现原理 https://blog.csdn.net/Z_Stand/article/details/104309019

          条件变量通知:  

            int pthread_cond_broadcast(pthread_cond_t *cond);  广播的方式触发所有的条件变量

            int pthread_cond_signal(pthread_cond_t *cond);    单个的触发条件变量

          读写锁:    对于读数据比修改数据频繁的应用,用读写锁代替互斥锁可以提高效率。

            如果当前线程读数据 则允许其他线程同时并发进行读操作 但不允许写操作

            如果当前线程写数据 则其他线程的读写都不允许操作

                                    RCU: 内核中RCU 做链表的操作

            pthread_rwlock_t  读写锁对象

            int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);  销毁读写锁

            int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);  初始化读写锁

             int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);   解除锁定

            *在mutex的基础上 区分了 读锁定 和写锁定

            读锁定 如果某线程申请了读锁定 其他线程依旧可以申请读锁 不能申请写锁定

                int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);    阻塞式申请读锁定

                int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);   非阻塞式申请读锁定

            写锁定 如果某线程申请了写锁定 则其他线程不能申请读锁定 也不能申请写锁定   

                int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);    申请写锁定

                int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);   非阻塞式申请写锁定


          线程信号:

            每个线程可以向其他线程发送信号 pthread_kill

            每个信号都有信号屏蔽集合

            同进程下所有线程共享对某信号的处理方法

            线程信号发送 

              int pthread_kill(pthread_t thread, int sig);      sig=0检测指定线程是否存在

              int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

                how = 阻塞:SIG_BLOCK  , 非阻塞:SIG_UNBLOCK  , 设置掩码:SIG_SETMASK

              不论给进程中的哪个线程发送SIG_KILL SIG_STOP 则当前进程中的所有线程都会推出

    #include<stdio.h>
    #include<pthread.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<signal.h>
    void *sigone_program(void *arg);
    void *sigtwo_program(void *arg);
    void report(int);
    pthread_t thread_one,thread_two;
    
    int main(int argc,char *argv[])
    {
            int i;
            void *status;
        if(pthread_create(&thread_one,NULL,sigone_program,NULL)!=0)                    //创建线程1
            {
                    fprintf(stderr,"pthread_create failure
    ");
                    exit(EXIT_FAILURE);
            }
            if(pthread_create(&thread_two,NULL,sigtwo_program,NULL)!=0)                //创建线程2
            {
                    fprintf(stderr,"pthread_create failure
    ");
                    exit(EXIT_FAILURE);
            }
            sleep(1);
    
            printf("this is parent ,send SIGUSR1,SIGUSR2 to thread %u
    ",thread_one);
    
            if(pthread_kill(thread_one,SIGUSR1)!=0)                                    //给指定线程发出已经屏蔽的信号
            {
                    perror("pthread_kill");
                    exit(EXIT_FAILURE);
            }
    
            if(pthread_kill(thread_one,SIGUSR2)!=0)                                    //给指定线程发出非屏蔽的信号
            {
                    perror("pthread_kill");
                    exit(EXIT_FAILURE);
            }
    
    
            printf("this is parent ,send SIGUSR1,SIGUSR2  to thread %u
    ",thread_two);
            if(pthread_kill(thread_two,SIGUSR1)!=0)
            {
                    perror("pthread_kill");
                    exit(EXIT_FAILURE);
            }
            if(pthread_kill(thread_two,SIGUSR2)!=0)
            {
                    perror("pthread_kill");
                    exit(EXIT_FAILURE);
            }
        sleep(1);
        if(pthread_kill(thread_one,SIGKILL)!=0)
            {
                    perror("pthread_kill");
                    exit(EXIT_FAILURE);
            }
    
            printf("the end
    ");
            pthread_join(thread_two,NULL);
            pthread_join(thread_one,NULL);
            return 0;
    }
    void *sigone_program(void *arg)            //线程函数1:屏蔽出SIGUSR2所有信号
    {
            int i;
            __sigset_t set;
        signal(SIGUSR1,report);
            sigfillset(&set);
            sigdelset(&set,SIGUSR2);
        pthread_sigmask(SIG_SETMASK,&set,NULL);
            for(i=0;i<5;i++)
            {
                printf("this is set mask %u thread
    ",pthread_self());
                pause();
            }
    }
    void report(int sig)                //打印当前线程的收到的信号和当前线程ID
    {
            printf("
    in signal ,the sig=%d	,the thread id=%u
    ",sig,pthread_self());
    }
    
    void *sigtwo_program(void *arg)            //线程函数2:绑定SIGUSR2的信号函数
    {
            int i;
            signal(SIGUSR2,report);
            for(i=0;i<5;i++)
            {
                    printf("this is no set mask %u thread
    ",pthread_self());
                pause();
        }
    }
    pthread_signal

          线程属性: 除非有特殊需求 , 不然,一般不会去设置线程属性的

    typedef struct __pthread_attr_s  线程属性的对象
    {
        int __detachstate;    设置可取消属性 
    
        int __schedpolicy;    调度策略
    
        struct __sched_param __schedparam;
    
        int __inheritsched;
    
        int __scope;
    
        size_t __guardsize;
        int __stackaddr_set;
    
        void *__stackaddr;
        size_t __stacksize;表示线程栈的大小。
    
    }pthread_attr_t; 
    在创建线程时有个 attr的参数,可以通过这个参数设置线程的属性 , 比如
    int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);   //设置attr对象的
    __detachstate
    int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);  //获取属性

    __detachstate:PTHREAD_CREATE_DETACHED--->不能被等待
            PTHREAD_CREATE_JOINABLE--->默认可以等待
    struct __pthread_attr_s attr;
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    pthrea_create(tid,&attr,func,argv);
    经过上面的设置后 线程不会被pthread_join()所等待了

    int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
    int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);

     

  • 相关阅读:
    设计模式-----简单工厂模式
    LeetCode题解002:两数相加
    LeetCode题解001:两数之和
    异常处理类-Throwable源码详解
    Windows下 gcc/g++的安装与配置
    Windows10下安装解压版MySQL教程
    Windows下Django项目搭建流程
    Linux域名服务DNS
    Linux文件共享服务 FTP,NFS 和 Samba
    操作系统:进程的概念和与程序的区别
  • 原文地址:https://www.cnblogs.com/yxnrh/p/12527115.html
Copyright © 2011-2022 走看看