返回顶部

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\n",*helloworld,helloworld);
    if(error=pthread_join(thread_id,(void **)&temptr))                    //等待子线程退出,并接受返回的堆指针
    {
        perror("pthread_join");
        exit(EXIT_FAILURE);    
    }
    printf("temp=%x,*temp=%c\n",temptr,*temptr);
    *temptr='d';                                                        //使用子线程创建的堆
    printf("%c\n",*temptr);    
    free(temptr);                                                        //释放
    return 0;
}

void *helloworld(char *argc)
{
    int *p;
    p=(int *)malloc(10*sizeof(int));                                //子线程创建了一个堆
    printf("the message is %s\n",argc);    
    printf("the child id is %u\n",pthread_self());
    memset(p,'c',10);
    printf("p=%x\n",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\n",pthread_self(),((int *)t)); 
} 

void * child1(void *arg) 
{ 
    int i=10;
    int tid=pthread_self(); 
    printf("\nset key value %d in thread %u\n",i,tid); 
    pthread_setspecific(key,&i);                                 //要使用特定的设置函数才能设置当前线程私有的变量
    printf("thread one sleep 2 until thread two finish\n");
    sleep(2); 
    printf("\nthread %u returns %d,add is %u\n",tid,*((int *)pthread_getspecific(key)),(int *)pthread_getspecific(key)); 
} 

void * child2(void *arg) 
{ 
    int temp=20;
    int tid=pthread_self(); 
    printf("\nset key value %d in thread %u\n",temp,tid); 
    pthread_setspecific(key,&temp); 
    sleep(1); 
    printf("thread %u returns %d,add is %u\n",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\n");
    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] != '\0')                                         //如果不是\0说明有字符还没读
            {
                pthread_mutex_unlock(&work_mutex);                            //解锁
                sleep(1);                                                    //休眠1秒让子线程读取完整
            }
            else 
            {
                break;                                                        //如果是\0 说明没有数据读了,则循环再次输入
            }
        }
    }
    pthread_mutex_unlock(&work_mutex);                                        //到了这里说明用户输入了end,解锁
    printf("\nWaiting for thread to finish...\n");
    res = pthread_join(a_thread, &thread_result);                            //阻塞等待子线程完成操作
    if (res != 0) 
    {
        perror("Thread join failed");
        exit(EXIT_FAILURE);
    }
    printf("Thread joined\n");
    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\n", strlen(work_area) -1);
        printf("the characters is %s",work_area);                //打印共享区域的内容
        work_area[0] = '\0';                                    //读完内容后那内容置0
        pthread_mutex_unlock(&work_mutex);                        //解锁
        sleep(1);                                                //休眠1秒再次让主线程上锁
        pthread_mutex_lock(&work_mutex);                        //上锁
        while (work_area[0] == '\0' )                             //判断是否有数据,一直循环直到有数据来
        {
            pthread_mutex_unlock(&work_mutex);                    //如果什么都没有解锁
            sleep(1);                                            //休眠1秒
            pthread_mutex_lock(&work_mutex);                    //上锁
        }
    }
    time_to_exit = 1;                                            //发出用户输入end想结束对话的信号状态
    work_area[0] = '\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\n");
                exit(EXIT_FAILURE);
        }
        if(pthread_create(&thread_two,NULL,sigtwo_program,NULL)!=0)                //创建线程2
        {
                fprintf(stderr,"pthread_create failure\n");
                exit(EXIT_FAILURE);
        }
        sleep(1);

        printf("this is parent ,send SIGUSR1,SIGUSR2 to thread %u\n",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\n",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\n");
        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\n",pthread_self());
            pause();
        }
}
void report(int sig)                //打印当前线程的收到的信号和当前线程ID
{
        printf("\nin signal ,the sig=%d\t,the thread id=%u\n",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\n",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);

 

 

posted on 2020-05-08 20:15  物有本末,事有终始  阅读(210)  评论(0编辑  收藏  举报

导航