【Linux】线程基本知识概述

忘是亡心i 2022-07-12 08:59 645阅读 0赞

本文内容概述:

1.线程的基本概念(包括线程的定义,线程之间的共享资源和私有资源);

2.基本函数(包括线程的创建,终止,等待,可分离和可切换,当然其中还会涉及互斥锁方面的内容等等)。

1.线程的基本概念:

在前边的学习中,我们知道,进程是在各自独立的地址空间中运行,如果需要共享资源,则要进行进程间的通信,比如管道,消息队列,信号量,共享内存这些 ,所以完成通信是比较困难的。而线程是进程内的一个执行分支,顾名思义,线程是在进程内部执行,占用着进程的地址空间,所以,线程之间的通信是比较容易的—-定义一个公共的buf,然后就能直接进行通信。

在Linux系统下,是没有线程 这个概念的,都是用进程去模拟线程。

我们看到的进程有可能是线程,所以,我们将线程称为轻量级进程。

由于线程是进程内部的执行流,所以线程就会共享一些进程的信息,当然也会有自己私有的信息:

线程共享进程的以下资源:

1)文件描述符信息;

2)每种信号的处理方式(由于线程是进程内部的执行流,所以每种信号的处理方式也是共享的);

3)当前工作目录;

4)各种id信息,比如用户id和组id信息。

线程也会有自己私有的信息:

1)线程id;

2)线程的上下文信息(每个线程都必须有自己的上下文信息,存储在自己的PCB中,当然有些操作系统中会 有一种存储线程信息的TCB结构体)

3)栈空间

4)调度优先级

5)errno变量

在Linux下的C api函数发生异常时,一般会将errno变量(在errno.h文件中)赋予一个整形值,不同的值代表不同的含义,可以通过输出的值来查看出错的信息。

比如:printf(“%d”,errno);就可以输出异常时的错误码

6)信号屏蔽字信息,这里的 信号屏蔽字就有点类似于文件的权限信息中的umask。

2.基本函数:

(1)线程的创建

Center

(2)线程的终止:

线程的终止方法是有3种:

a.直接在线程函数中return,但是在main函数中return,是表示整个进程的终止;

b.采用pthread_cancel函数让同一进程中的其他的线程去终止;

Center 1

程序举例:

  1. #include<stdio.h>
  2. #include<pthread.h>
  3. #include<unistd.h>
  4. void* pthreadRun(void* arg)
  5. {
  6. int count = 3;
  7. while(count--)
  8. {
  9. printf("new pthread,tid:%lu,pid:%d\n",pthread_self(),getpid());
  10. }
  11. }
  12. int main()
  13. {
  14. pthread_t tid = 0;
  15. int ret = pthread_create(&tid,NULL,pthreadRun,NULL);
  16. if(ret != 0)
  17. {
  18. perror("pthread_create");
  19. return -1;
  20. }
  21. if(ret != 0)
  22. {
  23. perror("pthread_detach");
  24. return -1;
  25. }
  26. sleep(1);
  27. if(pthread_cancel(tid)!= 0)
  28. {
  29. printf("cancel failed\n");
  30. return 1;
  31. }
  32. int exitCode;
  33. pthread_join(tid,(void**)&exitCode);
  34. printf("new pthread exit code:%d\n",exitCode);
  35. return 0;
  36. }

这段程序的运行结果是:

Center 2

解释: 由于在主线程中已经sleep了1秒之后,才终止新线程的,由于在1秒之内,新线程早已运行完成,所以,就会终止失败。

如果将终止新线程之前的sleep去掉,程序的运行结果究竟是什么呢?

Center 3

解释:新线程没有执行完成,就被主线程终止了,此时是终止成功的,所以退出码就是-1.

c.使用pthread_exit()自己终止自己。

Center 4

(3)线程的等待:

Center 5

(4)线程的可分离性:

一个线程是可结合的或者是可分离的。一个可结合的线程可以被与之在同一个进程中的其他线程所终止 ;而一个可分离的线程不需要主线程的等待,它的存储器等资源会在它运行结束时被操作系统回收。我们都知道,线程是进程内部的一个执行分支,为什么这么说?因为进程内的线程共享一个地址空间,所以在被分离的线程没有执行完成时,主线程不要退出,否则新的线程无法继续执行。主线程不可以等待被分离的线程。

Center 6

程序举例:

  1. #include<stdio.h>
  2. #include<pthread.h>
  3. #include<unistd.h>
  4. void* pthreadRun(void* arg)
  5. {
  6. int count = 3;
  7. while(count--)
  8. {
  9. printf("new pthread,tid:%lu,pid:%d\n",pthread_self(),getpid());
  10. sleep(1);
  11. }
  12. }
  13. int main()
  14. {
  15. pthread_t tid = 0;
  16. int ret = pthread_create(&tid,NULL,pthreadRun,NULL);
  17. if(ret != 0)
  18. {
  19. perror("pthread_create");
  20. return -1;
  21. }
  22. usleep(1000);
  23. ret = pthread_detach(tid);
  24. if(ret != 0)
  25. {
  26. perror("pthread_detach");
  27. return -1;
  28. }
  29. if(pthread_cancel(tid)!= 0)
  30. {
  31. printf("cancel failed\n");
  32. return 1;
  33. }
  34. int exitCode;
  35. if(0 == pthread_join(tid,(void*)&exitCode))
  36. {
  37. printf("wait success.\n");
  38. printf("new pthread exit code:%d\n",exitCode);
  39. }
  40. else
  41. {
  42. printf("wait failed\n");
  43. }
  44. return 0;
  45. }

程序运行结果:

Center 7

解释:从运行结果我们可以看出,我们不可以等待已经被分离的线程,但是可以终止已经分离的线程。

(5)线程的切换:

线程切换的条件:

a.一个时间片的完成;

b.操作系统模式的切换(内核态向用户态的切换)。

时间片的结束,会进行线程的切换,这个是比较容易理解的。

为什么操作系统模式的切换就会进行线程的切换?因为线程(在linux系统下,线程也被认为是进程,也有自己的PCB)的PCB是在内核中,只有内核态(也就是系统)才可以进行查看线程的信息,即就是访问PCB。我们知道,系统调用都是在内核态进行完成的,printf函数的底层调用的就是系统调用,所以我们采用printf函数进行显示数据的时候,就可能完成线程的切换。

所以,写一个程序,制造线程的切换。

  1. #include<stdio.h>
  2. #include<pthread.h>
  3. #include<unistd.h>
  4. static int count = 0;
  5. void* pthreadRun(void* arg)
  6. {
  7. int val = 0;
  8. int i = 0;
  9. while(i < 5000)
  10. {
  11. val = count;
  12. printf("new pthread,tid:%lu,pid:%d,count:%d\n",pthread_self(),getpid(),count);
  13. count = val + 1;
  14. i++;
  15. }
  16. return NULL;
  17. }
  18. int main()
  19. {
  20. pthread_t tid1 = 0;
  21. pthread_t tid2 = 0;
  22. int ret = pthread_create(&tid1,NULL,pthreadRun,NULL);
  23. if(ret != 0)
  24. {
  25. perror("pthread_create");
  26. return -1;
  27. }
  28. ret = pthread_create(&tid2,NULL,pthreadRun,NULL);
  29. if(ret != 0)
  30. {
  31. perror("pthread_create");
  32. return -1;
  33. }
  34. pthread_join(tid1,NULL);
  35. pthread_join(tid2,NULL);
  36. printf("count:%d\n",count);
  37. return 0;
  38. }

运行结果介于5000到10000之间不等。

如果我们给临界资源加锁,运行完代码之后解锁。代码如下:

  1. #include<stdio.h>
  2. #include<pthread.h>
  3. #include<unistd.h>
  4. static int count = 0;
  5. pthread_mutex_t myLock = PTHREAD_MUTEX_INITIALIZER;
  6. void* pthreadRun(void* arg)
  7. {
  8. int val = 0;
  9. int i = 0;
  10. while(i < 3000)
  11. {
  12. pthread_mutex_lock(&myLock);
  13. val = count;
  14. printf("new pthread,tid:%lu,pid:%d,count:%d\n",pthread_self(),getpid(),count);
  15. count = val + 1;
  16. i++;
  17. pthread_mutex_unlock(&myLock);
  18. }
  19. return NULL;
  20. }
  21. int main()
  22. {
  23. pthread_t tid1 = 0;
  24. pthread_t tid2 = 0;
  25. int ret = pthread_create(&tid1,NULL,pthreadRun,NULL);
  26. if(ret != 0)
  27. {
  28. perror("pthread_create");
  29. return -1;
  30. }
  31. ret = pthread_create(&tid2,NULL,pthreadRun,NULL);
  32. if(ret != 0)
  33. {
  34. perror("pthread_create");
  35. return -1;
  36. }
  37. pthread_join(tid1,NULL);
  38. pthread_join(tid2,NULL);
  39. pthread_mutex_destroy(&myLock);
  40. printf("count:%d\n",count);
  41. return 0;
  42. }

这样对临界资源进行加锁之后,就可以保证全局变量最终可以加至6000.

关于互斥锁的函数的声明:

1)初始化和销毁函数

Center 8

2)申请和释放锁资源函数

Center 9

为什么加锁就可以防止线程之间相互干扰?

那是因为加锁就可以保证锁之间的代码是一个原子操作(要么执行就能执行完,要么不执行,也就是非0即1的状态)。

那么lock()和unlock()函数是如何实现的呢?

方法一:

Center 10

解释:我们知道mutex = 0;这一步并不是原子操作,如果两个线程同时调用lock函数,都判断出mutex > 0,然后其中一个线程将mutex置为0,另一个线程并不知道此事,也将mutex置为0,于是两个线程都以为自己获得了锁资源,这样就发生了冲突。所以这种办法是不可行的。

执行赋值操作需要几步呢?

步骤一:将mutex的数据从内存读到寄存器;

步骤二:通过CPU内的运算器将mutex进行赋值;

步骤三:将mutex的值重新写回内存。

方法二:

Center 11

解释:由于方法一中的赋值操作不是原子操作,导致上述方法不可取。为了解决这个问题,大多体系结构提供了swap或者exchange命令,将寄存器和内存里的值进行交换,就保证了原子操作。

lock函数 语句解释:

将al寄存器中的值放置为0;

将al寄存器和内存中的mutex的值进行交换;

……

如果执行了xchgb指令之后,又有一个线程申请锁资源,此时它看到的mutex就是0,表示不可以申请到资源;

如果执行了movb指令之后,又有一个线程申请锁资源,此时哪个线程获得锁资源,就看自己的优先级或者权限等等问题了。

所以,这个方法就是lock函数和unlock函数里执行的操作了。

关于线程的基本知识就先整理到这里~~

发表评论

表情:
评论列表 (有 0 条评论,645人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Linux线基本知识概述

    本文内容概述: 1.线程的基本概念(包括线程的定义,线程之间的共享资源和私有资源); 2.基本函数(包括线程的创建,终止,等待,可分离和可切换,当然其中还会涉及互斥锁方面的

    相关 线基本知识总结

    (一)创建线程的方式 (1)实现Runnable接口 (2)继承Thread类 推荐使用接口,能够做到定义与实现分离,耦合更低 (二)关于线程的优先级 thr

    相关 线基本知识

    1.了解什么是线程(what) 要知道线程,首先要知道什么是进程? 进程:正在执行的(进行的)程序,需要操作系统为其分配独立的内存地址空间。 线程:为了提高效率(目的),