• 线程--同步--互斥--死锁


    什么是线程?与进程之间的关系?

    进程:是cpu分配资源的最小单位

    线程:是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位

    进程只是维护程序所需的资源,线程才是真正的执行体.所以一个进程至少包含一个线程.

    多线程的好处?

    • 在只有一个控制线程下,单线程进程要完成多个任务,多线程下只需要把任务分解,为每个任务分配一个单独的线程,相互独立的任务就可以交叉进行,提高代码的吞吐量        
    • 多线程自动的访问相同的存储地址空间和文件描述符

    一个进程的所有信息对该进程的所有线程都是共享的

    每个线程都含有表示执行环境所必须的信息(非共享的 )

    1)线程id

    4) errno变量

    5) 信号屏蔽字

    6) 调度优先级

     线程标识

    进程id在整个系统是唯一的,但线程id不同,只在他所属进程上下文中才有意义.

    进程id使用pid-t数据类型表示,是一个非负整数.

    线程id是用pthread_t数据类型表示,实现的时候用一个结构来代表pthread_t数据类型,所以可移植操作系统实现不能把他作为整数处理,

     用结构表示pthread_t数据类型,我们可以通过pthread_self(获取线程id)pthread_equal(比较是否相等)

    1. #include
    2. #include
    3. int main()
    4. {
    5. pthread_t tid;
    6. tid=pthread_self();
    7. printf("tid=%d.....\n",tid);
    8. if(pthread_equal(tid,pthread_self()))
    9. {
    10. printf("equal............\n");
    11. }
    12. else{
    13. printf("no equal......\n");
    14. }
    15. return 0;
    16. }

    线程的创建

    1. #include
    2. int pthread_create(pthread_t *thread,
    3. const pthread_attr_t *attr,
    4. void *(*start_routine)(void *),
    5. void *arg );
    6. 功能:
    7. 创建一个线程。
    8. 参数:
    9. thread:线程标识符地址。
    10. attr:线程属性结构体地址,通常设置为 NULL
    11. start_routine:线程函数的入口地址。
    12. arg:传给线程函数的参数。
    13. 返回值:
    14. 成功:0
    15. 失败:非 0
    1. #include
    2. #include
    3. #include
    4. void* fun(void *arg)
    5. {
    6. pthread_t ntid;
    7. printf("fun:%d\n",pthread_self());
    8. printf("pthread......run......\n");
    9. return (void*)0;
    10. }
    11. int main()
    12. {
    13. pthread_t tid;
    14. pid_t pid;
    15. pthread_create(&tid,NULL,fun,0);
    16. sleep(2);//主线程需要休眠,否则新线程创建不出来就退出了
    17. tid=pthread_self();
    18. printf("main:%d,%d\n",tid,getpid());
    19. return 0;
    20. }

    线程终止

    任意线程调用exit.和_exit会使整个进程终止

    单个线程可以通过3种方式退出(在进程不终止的情况下)

    • 从执行函数中退出
    • 调用pthread_exit退出线程
    • 可以被同一进程其他线程取消
    1. #include
    2. void pthread_exit(void *retval);
    3. 功能:
    4. 退出调用线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
    5. 参数:
    6. retval:存储线程退出状态的指针。
    7. 返回值:无
    1. #include
    2. int pthread_join(pthread_t thread, void **retval);
    3. 功能:
    4. 等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
    5. 参数:
    6. thread:被等待的线程号。
    7. retval:用来存储线程退出状态的指针的地址。
    8. 返回值:
    9. 成功:0
    10. 失败:非 0

     如果对返回值不感兴趣就将rval_ptr设为null,不获取线程的终止状态

    如何让获取已终止程序的状态码

    1. #include
    2. #include
    3. W>void* fun1(void*arg)
    4. {
    5. printf("fun1....return ......\n");
    6. return (void*)123;
    7. }
    8. W>void * fun2(void *arg)
    9. {
    10. printf("fun2......exit...........\n");
    11. pthread_exit((void*)2);
    12. }
    13. int main()
    14. {
    15. pthread_t tid1;
    16. pthread_t tid2;
    17. //状态码
    18. void *ret;
    19. //创建线程
    20. pthread_create(&tid1,NULL,fun1,0);
    21. pthread_create(&tid2,NULL,fun2,0);
    22. //等待结束
    23. pthread_join(tid1,&ret);
    24. printf("thread 1 return.....%ld\n",(long)ret);
    25. pthread_join(tid2,&ret);
    26. printf("thread 2 exit.......%ld\n",(long)ret);
    27. return 0;
    28. }

    无类型指针参数可以传递包含复杂信息的结构的地址

     变量(分配在栈上)作为pthread_exit的参数时出现问题

    1. #include
    2. #include
    3. #include
    4. struct data {
    5. int a,b,c,d;
    6. };
    7. void printfdata(char*s,struct data*fd)
    8. {
    9. printf("%s",s);
    10. printf("struct at 0x%lx\n",(long)fd);
    11. printf("fd->a:%d\n",fd->a);
    12. printf("fd->b:%d\n",fd->b);
    13. printf("fd->c:%d\n",fd->c);
    14. printf("fd->d:%d\n",fd->d);
    15. }
    16. void *fun1(void*arg)
    17. {
    18. struct data fd={1,2,3,4};
    19. printfdata("thread 1\n",&fd);
    20. pthread_exit((void*)&fd);
    21. }
    22. void *fun2(void *arg)
    23. {
    24. printf("thread 2:id is %lu\n",pthread_self());
    25. pthread_exit((void*)0);
    26. }
    27. int main()
    28. {
    29. pthread_t tid1;
    30. pthread_t tid2;
    31. struct data *fd;
    32. //创建线程
    33. int ret= pthread_create(&tid1,NULL,fun1,NULL);
    34. if(ret!=0)
    35. {
    36. printf("thread 1 error..........\n");
    37. return 1;
    38. }
    39. ret=pthread_join(tid1,(void *)&fd);
    40. if(ret!=0)
    41. {
    42. printf("join.........error\n");
    43. return 1;
    44. };
    45. sleep(2);
    46. printf("starting second thread.....\n");
    47. pthread_create(&tid2,NULL,fun2,NULL);
    48. sleep(1);
    49. printfdata("second:\n",fd);
    50. return 0;
    51. }

     线程可以调用pthread_cancel()函数来请求取消同一进程的其他进程(只是请求并不一定取消)

    线程分离(简单说一下)

    一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。

    不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。

    1. #include
    2. int pthread_detach(pthread_t thread);
    3. 功能:
    4. 使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,
    5. 线程分离的目的是将线程资源的回收工作交由系统自动来完成,也就是说当
    6. 被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。
    7. 参数:
    8. thread:线程号。
    9. 返回值:
    10. 成功:0
    11. 失败:非0

    1. #include
    2. #include
    3. #include
    4. W>void*fun(void*arg)
    5. {
    6. printf("cccccccccccccccc\n");
    7. pthread_exit((void*)1);
    8. }
    9. int main()
    10. {
    11. pthread_t tid;
    12. pthread_create(&tid,NULL,fun,NULL);
    13. sleep(2);//保证线程创建成功
    14. // 设置分离
    15. pthread_detach(tid);
    16. return 0;

    线程同步

    两个或两个以上线程在访问同一种资源先后顺序完成指定任务

    如果两个或两个以上线程不同步会出现数据错误

    使用着打印机打印东西的同时(还没有打印完),别人刚好也在此刻使用打印机打印东西,如果不做任何处理的话,打印出来的东西肯定是错乱的。

    例子

    1. #include
    2. #include
    3. #include
    4. void *fun1(void*arg)
    5. {
    6. for(int i='A';i<='Z';i++)
    7. {
    8. printf("%c",i);
    9. fflush(stdout);
    10. usleep(100000);
    11. }
    12. pthread_exit((void*)1);
    13. }
    14. W>void*fun2(void*arg)
    15. {
    16. for(int i='a';i<='z';i++)
    17. {
    18. printf("%c",i);
    19. fflush(stdout);
    20. usleep(100000);
    21. }
    22. pthread_exit((void*)2);
    23. }
    24. int main()
    25. {
    26. pthread_t tid1;
    27. pthread_t tid2;
    28. pthread_create(&tid1,NULL,fun1,NULL);
    29. pthread_create(&tid2,NULL,fun2,NULL);
    30. sleep(2);
    31. printf("\n");
    32. pthread_detach(tid1);
    33. pthread_detach(tid2);
    34. return 0;
    35. }

    为了避免数据发生错误

    对公共资源加锁,在该线程访问时,禁止其他进程使用,完成后解锁

    互斥量

     pthread_mutex_init函数

    1. #include
    2. int pthread_mutex_init(pthread_mutex_t *restrict mutex,
    3. const pthread_mutexattr_t *restrict attr);
    4. 功能:
    5. 初始化一个互斥锁。
    6. 参数:
    7. mutex:互斥锁地址。类型是 pthread_mutex_t
    8. attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL
    9. 可以使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,比如:
    10. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    11. 这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init() 来完成动态初始化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查。
    12. 返回值:
    13. 成功:0,成功申请的锁默认是打开的。
    14. 失败:非 0 错误码

     pthread_mutex_lock函数

    1. #include
    2. int pthread_mutex_lock(pthread_mutex_t *mutex);
    3. 功能:
    4. 对互斥锁上锁,若互斥锁已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁。
    5. 参数:
    6. mutex:互斥锁地址。
    7. 返回值:
    8. 成功:0
    9. 失败:非 0 错误码
    10. int pthread_mutex_trylock(pthread_mutex_t *mutex);
    11.  调用该函数时,若互斥锁未加锁,则上锁,返回 0
    12.  若互斥锁已加锁,则函数直接返回失败,即 EBUSY。

    pthread_mutex_destroy函数 

    1. #include
    2. int pthread_mutex_destroy(pthread_mutex_t *mutex);
    3. 功能:
    4. 销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。
    5. 参数:
    6. mutex:互斥锁地址。
    7. 返回值:
    8. 成功:0
    9. 失败:非 0 错误码

      pthread_mutex_unlock函数

    1. #include
    2. int pthread_mutex_unlock(pthread_mutex_t *mutex);
    3. 功能:
    4. 对指定的互斥锁解锁。
    5. 参数:
    6. mutex:互斥锁地址。
    7. 返回值:
    8. 成功:0
    9. 失败:非0错误码

    实例:

    1. pthread_mutex_t mutex; //互斥锁
    2. // 打印机
    3. void printer(char *str)
    4. {
    5. pthread_mutex_lock(&mutex); //上锁
    6. while (*str != '\0')
    7. {
    8. putchar(*str);
    9. fflush(stdout);
    10. str++;
    11. sleep(1);
    12. }
    13. printf("\n");
    14. pthread_mutex_unlock(&mutex); //解锁
    15. }
    16. // 线程一
    17. void *thread_fun_1(void *arg)
    18. {
    19. char *str = "hello";
    20. printer(str); //打印
    21. }
    22. // 线程二
    23. void *thread_fun_2(void *arg)
    24. {
    25. char *str = "world";
    26. printer(str); //打印
    27. }
    28. int main(void)
    29. {
    30. pthread_t tid1, tid2;
    31. pthread_mutex_init(&mutex, NULL); //初始化互斥锁
    32. // 创建 2 个线程
    33. pthread_create(&tid1, NULL, thread_fun_1, NULL);
    34. pthread_create(&tid2, NULL, thread_fun_2, NULL);
    35. // 等待线程结束,回收其资源
    36. pthread_join(tid1, NULL);
    37. pthread_join(tid2, NULL);
    38. pthread_mutex_destroy(&mutex); //销毁互斥锁
    39. return 0;
    40. }

    死锁

     

     

    1)什么是死锁?

    死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,永远在互相等待

    2)死锁引起的原因?

    • 竞争不可抢占资源引起死锁
    • 竞争可消耗资源引起死锁
    • 进程推进顺序不当引起死锁

    3)死锁的必要条件

    • 互斥条件:  某资源只能被一个进程使用,其他进程请求该资源时,只能等待,直到资源使用完毕后释放资源。
    • 请求和保持条件:  程序已经保持了至少一个资源,但是又提出了新要求,而这个资源被其他进程占用,自己占用资源却保持不放。
    • 不可抢占条件:  进程已获得的资源没有使用完,不能被抢占。
    • 循环等待条件:  必然存在一个循环链。

    4)处理死锁的思路

    • 预防死锁

    破坏死锁的四个必要条件中的一个或多个来预防死锁。

    • 避免死锁

    和预防死锁的区别就是,在资源动态分配过程中,用某种方式防止系统进入不安全的状态。

    • 检测死锁

    运行时出现死锁,能及时发现死锁,把程序解脱出来

    • 解除死锁

    发生死锁后,解脱进程,通常撤销进程,回收资源,再分配给正处于阻塞状态的进程。

     5)预防死锁的方法

    破坏请求和保持条件

    协议1:

    所有进程开始前,必须一次性地申请所需的所有资源,这样运行期间就不会再提出资源要求,破坏了请求条件,即使有一种资源不能满足需求,也不会给它分配正在空闲的资源,这样它就没有资源,就破坏了保持条件,从而预防死锁的发生。

    协议2:

    允许一个进程只获得初期的资源就开始运行,然后再把运行完的资源释放出来。然后再请求新的资源。

    破坏不可抢占条件

    当一个已经保持了某种不可抢占资源的进程,提出新资源请求不能被满足时,它必须释放已经保持的所有资源,以后需要时再重新申请。

    破坏循环等待条件

    对系统中的所有资源类型进行线性排序,然后规定每个进程必须按序列号递增的顺序请求资源。假如进程请求到了一些序列号较高的资源,然后有请求一个序列较低的资源时,必须先释放相同和更高序号的资源后才能申请低序号的资源。多个同类资源必须一起请求。

  • 相关阅读:
    阿里二面:如何定位&避免死锁?连着两个面试问到了!
    Java 基础数据类型占用内存空间和字符串编码简介(二)
    《CTFshow-Web入门》10. Web 91~110
    springboot源码理解六、run方法执行过程(获取启动监听器、构建上下文环境、初始化应用上下文)
    基于Python网络爬虫的二手房数据采集及可视化分析项目源码+使用教程+爬虫+报告PPT+详细注释(高分毕业设计)+全部数据
    python+django网吧会员管理系统
    elementui限制input输入框中小数点保留一位小数
    Java,设计,功能权限和数据权限,用户、角色、权限和用户组
    股权项目披露:扬州国扬电子有限公司6.2664%股权转让
    可变字符串
  • 原文地址:https://blog.csdn.net/weixin_58389786/article/details/126435924