Linux 系统应用编程——线程基础

传送门:Linux多线程编程实例解析 . 

                    linux多线程编程——同步与互斥 .

    

       传统多任务操作系统中一个可以独立调度的任务(或称之为顺序执行流)是一个进程。每个程序加载到内存后只可以唯一地对应创建一个顺序执行流,即传统意义的进程。每个进程的全部系统资源是私有的,如虚拟地址空间,文件描述符和信号处理等等。使用多进程实现多任务应用时存在如下问题:

1)任务切换,即进程间上下文切换,系统开销比较大。(虚拟地址空间以及task_struct 都需要切换)

2)多任务之间的协作比较麻烦,涉及进程间通讯。(因为不同的进程工作在不同的地址空间)

所以,为了提高系统的性能,许多操作系统规范里引入了轻量级进程的概念,也被称为线程

 

一、线程基础

      通常线程指的是共享相同地址空间的多个任务。线程最大的特点就是在同一个进程中创建的线程共享该进程的地址空间;但一个线程仍用task_struct 来描述,线程和进程都参与统一的调度。所以,多线程的好处便体现出来:

1)大大提高了任务切换的效率;因为各线程共享进程的地址空间,任务切换时只要切换task_struct 即可;

2)线程间通信比较方便;因为在同一块地址空间,数据共享;

当然,共享地址空间也会成为线程的缺点,因为共享地址空间,如果其中一个线程出现错误(比如段错误),整个线程组都会崩掉!

     Linux之所以称呼其线程为LWP( Light Weight Process ),因为从内核实现的角度来说,它并没有为线程单独创建一个结构,而是继承了很多进程的设计:

1)继承了进程的结构体定义task_struct ;

2)没有专门定义线程ID,复用了PID;

3)更没有为线程定义特别的调度算法,而是沿用了原来对task_struct 的调度算法。

最新的Linux内核里线程已经替代原来的进程称为调度的实际最小单位

原来的进程概念可以看成是多个线程的容器,称之为线程组;即一个进程就是所有相关的线程构成的一个线程组。传统的进程等价于单线程进程

每个线程组都有自己的标识符 tgid (数据类型为 pid_t ),其值等于该进程(线程组)中的第一个线程(group_leader)的PID。

 

1、创建线程

 pthread_create()函数描述如下:

所需头文件#include <pthread.h>
函数原型

int pthread_create(pthread_t *thread,const pthread_attr_t *attr,

                               void *(* routine)(void *), void *arg)

函数参数

thread :创建的线程

attr :指定线程的属性,NULL表示使用缺省属性

routine :线程执行的函数

arg :传递给线程执行的函数的参数

函数返回值

成功: 0

出错: -1

1)这里routine 是回调函数(callback),其函数类型由内核来决定,这里我们将其地址传给内核;这个函数并不是线程创建了就会执行,而是只有当其被调度到cpu上时才会被执行;具体回调函数的讲解,移步Linux C 函数指针应用---回调函数 .;

2)arg 是线程执行函数的参数,这里我们将其地址穿进去,使用时需要先进行类型转换,才能使用;如果参数不止一个,我们可以将其放入到结构体中;

 

2、pthread_join () 函数

其函数描述如下:

所需头文件#include <pthread.h>
函数原型int thread_join(pthread_t thread, void  ** value_ptr)
函数参数

thread :要等待的线程

value_ptr :指针 *value_ptr 指向线程返回的参数

函数返回值

成功: 0

出错: -1

这里,我们可以看到 value_ptr 是个二级指针,其是出参,存放的是线程返回参数的地址;

 

3、pthread_exit 函数

其函数描述如下:

所需头文件#include <pthread.h>
函数原型int pthread_exit(void *value_ptr)
函数参数value_ptr :线程退出时返回的值
函数返回值

成功:0

出错:-1

和进程中的exit() 、wait()一样,这里pthread_join 与 pthread_exit 是工作在两个线程之中;

 

下面看一个实例:

[cpp] view plaincopy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <pthread.h>  
  5.   
  6. char message[32] = "Hello World!";  
  7. void *thread_function(void *arg);  
  8.   
  9. int main()  
  10. {  
  11.     pthread_t a_thread;  
  12.     void *thread_result;  
  13.   
  14.     if(pthread_create(&a_thread,NULL,thread_function,(void *)message) < 0)  
  15.     {  
  16.         perror("fail to pthread_create");  
  17.         exit(-1);  
  18.     }  
  19.   
  20.     printf("waiting for thread to finish\n");  
  21.     if(pthread_join(a_thread,&thread_result) < 0)  
  22.     {  
  23.         perror("fail to pthread_join");  
  24.         exit(-1);  
  25.     }  
  26.   
  27.     printf("Message is now %s\n",message);  
  28.     printf("thread_result is %s\n",(char *)thread_result);  
  29.     return 0;  
  30. }  
  31.   
  32. void *thread_function(void *arg)  
  33. {  
  34.     printf("thread_function is running,argument is %s\n",(char *)arg);  
  35.     strcpy(message,"marked by thread");  
  36.     pthread_exit("Thank you for the cpu time");  
  37. }  

编译

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/thread/0107$ gcc -o thread thread.c -lpthread  
  2. fs@ubuntu:~/qiang/thread/0107$   

线程通过第三方的线程库来实现,所以这里要 -lpthread ,-l 是链接一个库,这个库是pthread;

执行结果如下:

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/thread/0107$ ./thread   
  2. waiting for thread to finish  
  3. thread_function is running,argument is Hello World!  
  4. Message is now marked by thread  
  5. thread_result is Thank you for the cpu time  
  6. fs@ubuntu:~/qiang/thread/0107$   

从这个程序,我们可以看到线程之间是如何通信的,线程之间通过二级指针来传送参数的地址(这是进程所不具备的,因为他们的地址空间独立),但两个线程之间的通信,传递的数据的生命周期必须是静态的。可以使全局变量、static修饰的数据、堆里面的数据;这个程序中的message就是一个全局变量。其中一个线程可以修改它,另一个线程得到它修改过后的message。

 

二、线程的同步和互斥

先来了解同步和互斥的基本概念:

临界资源:某些资源来说,其在同一时间只能被一段机器指令序列所占用。这些一次只能被一段指令序列所占用的资源就是所谓的临界资源。

临界区:对于临界资源的访问,必须是互斥进行。也就是当临界资源被一个指令序列占用时,另一个需要访问相同临界资源的指令序列就不能被执行。指令序列不能执行的实际意思就是其所在的进程/线程会被阻塞。所以我们定义程序内访问临界资源的代码序列被称为临界区。

互斥:是指同事只允许一个访问者对临界资源进行访问,具有唯一性排它性。但互斥无法限制访问这个对资源的访问顺序,即访问时无序的。

同步:是指在互斥的基础上,通过其他机制实现访问者对资源的有序访问。

 

1、线程间互斥

引入互斥(mutual   exlusion)锁的目的是用来保证共享数据的完整性。

互斥锁主要用来保护临界资源。每个临界资源都有一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源;线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止;

通常,我们在临界区前上锁,临界区后解锁

1)初始化互斥锁函数

所需头文件#include <pthread.h>
函数原型

int pthread_mutex_init (pthread_mutex_t  *mutex,  pthread_mutexattr_t  *attr )

//初始化互斥锁

函数参数

mutex:互斥锁

attr :互斥锁属性 // NULL表示缺省属性

函数返回值

成功:0

出错:-1

2)申请互斥锁函数

所需头文件#include <pthread.h>
函数原型

int pthread_mutex_lock(pthread_mutex_t *mutex)

//申请互斥锁

函数参数

mutex:互斥锁

函数返回值

成功:0

出错:-1

3)释放互斥锁函数

所需头文件#include <pthread.h>
函数原型

int pthread_mutex_unlock(pthread_mutex_t *mutex)

//释放互斥锁

函数参数

mutex:互斥锁

函数返回值

成功:0

出错:-1

下面是一个实例:

[cpp] view plaincopy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <pthread.h>  
  5. #include <unistd.h>  
  6. //#define _LOCK_  
  7.   
  8. unsigned int value1,value2,count;  
  9. pthread_mutex_t mutex;  
  10. void *function(void *arg);  
  11.   
  12. int main()  
  13. {  
  14.     pthread_t a_thread;  
  15.   
  16.     if(pthread_mutex_init(&mutex,NULL) < 0)  
  17.     {  
  18.         perror("fail to mutex_init");  
  19.         exit(-1);  
  20.     }  
  21.   
  22.     if(pthread_create(&a_thread,NULL,function,NULL) != 0)  
  23.     {  
  24.         perror("fail to pthread_create");  
  25.         exit(-1);  
  26.     }  
  27.   
  28.     while(1)  
  29.     {  
  30.         count++;  
  31. #ifdef _LOCK_  
  32.         pthread_mutex_lock(&mutex);  
  33. #endif  
  34.         value1 = count;  
  35.         value2 = count;  
  36. #ifdef _LOCK_  
  37.         pthread_mutex_unlock(&mutex);  
  38. #endif  
  39.     }  
  40.     return 0;  
  41. }  
  42.   
  43. void *function(void *arg)  
  44. {  
  45.     while(1)  
  46.     {  
  47. #ifdef _LOCK_  
  48.         pthread_mutex_lock(&mutex);  
  49. #endif  
  50.         if(value1 != value2)  
  51.         {  
  52.             printf("count = %d,value1 = %d,value2 = %d\n",count,value1,value2);  
  53.             usleep(100000);  
  54.         }  
  55. #ifdef _LOCK_  
  56.         pthread_mutex_unlock(&mutex);  
  57. #endif  
  58.     }  
  59.     return NULL;  
  60. }  

执行结果如下:

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/thread/0107$ ./mutex  
  2.   
  3. count = 3368408,value1 = 3368408,value2 = 3368407  
  4. count = 44174760,value1 = 44174760,value2 = 44174759  
  5. count = 69313865,value1 = 69313865,value2 = 69313864  
  6. count = 139035309,value1 = 139035309,value2 = 139035308  
  7. count = 168803956,value1 = 168803956,value2 = 168803955  
  8. count = 192992611,value1 = 192992611,value2 = 192992610  
  9. count = 224279903,value1 = 224279903,value2 = 224279902  
  10. count = 259586793,value1 = 259586793,value2 = 259586792  
  11. count = 282057307,value1 = 282057307,value2 = 282057306  
  12. count = 321607823,value1 = 321607823,value2 = 321607822  
  13. count = 351629940,value1 = 351629940,value2 = 351629939  
  14. count = 374130545,value1 = 374130545,value2 = 374130544  
  15. count = 400727525,value1 = 400727525,value2 = 400727524  
  16. count = 440219988,value1 = 440219988,value2 = 440219987  
  17. count = 466069865,value1 = 466069865,value2 = 466069864  
  18. count = 500581241,value1 = 500581241,value2 = 500581240  
  19. count = 522649671,value1 = 522649671,value2 = 522649670  
  20. count = 569234325,value1 = 569234325,value2 = 569234324  
  21. count = 608139152,value1 = 608139152,value2 = 608139151  
  22. count = 639493957,value1 = 639493957,value2 = 639493956  
  23. .....  
 

我们可以看到,数据是不断被打印的,说明 a 线程是可以访问临界资源的。

我们把#define  _LOCK_前面的注释去掉,这时就加上了互斥锁,执行结果如下:

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/thread/0107$ ./mutex  

此时,并没有数据被打印,说明此时a线程中 value1 与 value 2 一直是相等的,说明主线程执行是,a线程并无法访问临界资源的。

2、线程间同步

同步(synchronization) 指的是多个任务(线程)按照约定的顺序相互配合完成一件事情;

线程间同步——P / V 操作

信号量代表某一类资源,其值表示系统中该资源当前可用的数量。

信号量是一个受保护的变量,只能通过三种操作来访问:

1)初始化

2)P操作(申请资源)

3)V操作(释放资源)P(S)含义如下:

[cpp] view plaincopy
  1. if (信号量的值大于0)  
  2. {  
  3.     请资源的任务继续运行;  
  4.     信号量的值 减一;  
  5. }  
  6. else  
  7. {  
  8.     请资源的任务阻塞;  
  9. }  
 

V(S)含义如下:

[cpp] view plaincopy
  1. if (没有任务在等待该资源)  
  2. {  
  3.     信号量的值 加一;  
  4. }  
  5. else  
  6. {  
  7.     唤醒第一个等待的任务,让其继续运行;  
  8. }  

1)、信号量初始化函数:

所需头文件#include <semaphore.h>
函数原型

int sem_int (sem_t *sem,int pshared,unsigned int value)

//初始化信号量

函数参数

sem:初始化的信号量

pshared:信号量共享的范围(0:线程间使用 非0 :进程间使用)

value :信号量初值

函数返回值

成功:0

出错:-1


2)P操作

所需头文件#include <semaphore.h>
函数原型

int sem_wait (sem_t *sem) //P操作

函数参数

sem:信号量

函数返回值

成功:0

出错:-1

3)V操作

所需头文件#include <semaphore.h>
函数原型

int sem_post(sem_t *sem) //V操作

函数参数

sem:信号量

函数返回值

成功:0

出错:-1


下面是个实例:

[cpp] view plaincopy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <pthread.h>  
  5. #include <semaphore.h>  
  6.   
  7. char buf[60];  
  8. sem_t sem;  
  9. void *function(void *arg);  
  10.   
  11. int main(int argc, char *argv[])  
  12. {  
  13.     pthread_t a_thread;  
  14.     void *thread_result;  
  15.   
  16.     if(sem_init(&sem,0,0) != 0)  
  17.     {  
  18.         perror("fail to sem_init");  
  19.         exit(-1);  
  20.     }  
  21.   
  22.     if(pthread_create(&a_thread,NULL,function,NULL) != 0)  
  23.     {  
  24.         perror("fail to pthread_create");  
  25.         exit(-1);  
  26.     }  
  27.   
  28.     printf("input 'quit' to exit\n");  
  29.     do    
  30.     {  
  31.         fgets(buf,60,stdin);  
  32.         sem_post(&sem);  
  33.     }  
  34.     while(strncmp(buf,"quit",4) != 0);  
  35.       
  36.     return 0;  
  37. }  
  38.   
  39. void *function(void *arg)  
  40. {  
  41.     while(1)  
  42.     {  
  43.         sem_wait(&sem);  
  44.         printf("you enter %d characters\n",strlen(buf) - 1);  
  45.     }  
  46. }  

执行结果如下:

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/thread/0107$ ./sem   
  2. input 'quit' to exit  
  3. xiao  
  4. you enter 4 characters  
  5. zhi  
  6. you enter 3 characters  
  7. qiang  
  8. you enter 5 characters  
  9. quit  
  10. fs@ubuntu:~/qiang/thread/0107$   

我们可以看到两个线程是同步的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/402286.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用indent格式化源文件的脚本

脚本一&#xff1a;格式化指定目录下的源文件(*.h, *.cpp...) #!/bin/sh# 格式化某目录下所有*.h, *.c, *.cpp, *.hh文件, 并将文件换行符转换成Linux下的格式if [ $# -lt 1 ]; thenecho "Usage: $0 <dir>"exit 1elsedir$1fi# format a source file(*.c, *.h,…

apple mach-o linker (id) error

在网上查了下&#xff0c;有网友说是因为有变量重名&#xff0c;仔细检查了下代码&#xff0c;UIGraphicsEndImageContext()写成uigraphicsendimagecontext()了&#xff0c;改过来就编译通过了。转载于:https://www.cnblogs.com/cc-Cheng/p/3341242.html

Struts入门(三)深入Struts用法讲解

访问Servlet APIAction搜索顺序动态方法调用指定多个配置文件默认ActionStruts 后缀接收参数处理结果类型1.访问Servlet API 首先我们了解什么是Servlet API httpRequest、httpResponse、servletContext  3个api对应jsp面向对象&#xff1a;request、response、application …

linux errno定义

以下内容来自于Ubuntu系统&#xff0c;请看执行情况&#xff1a; [zcmasm-generic #6]$pwd /usr/include/asm-generic [zcmasm-generic #7]$ls errno* -lh -rw-r--r-- 1 root root 1.6K Jun 19 2013 errno-base.h -rw-r--r-- 1 root root 5.2K Jun 19 2013 errno.h [zcmasm-g…

linux多线程编程——同步与互斥

一、 为什么要用多线程技术&#xff1f; 1、避免阻塞&#xff0c;大家知道&#xff0c;单个进程只有一个主线程&#xff0c;当主线程阻塞的时候&#xff0c;整个进程也就阻塞了&#xff0c;无法再去做其它的一些功能了。 2、避免CPU空转&#xff0c;应用程序经常会涉及到RPC&am…

黑马程序员_泛型

--------------------ASP.NetAndroidIOS开发、.Net培训、期待与您交流&#xff01; -------------------- 1. 泛型 1.概述 泛型是为了解决了集合中存储对象安全问题&#xff0c;如果集合中存数了不同类型的对象&#xff0c;那么读取出来后&#xff0c;操作取出的对象以为不…

菜鸟成长记(十一)----- 操蛋的2016与未知的2017

现在已经2017.1.16号了&#xff0c;早就说着要写篇总结&#xff0c;骂骂特么操蛋的自己&#xff0c;当然这两三年来在这里骂的真特么不在少数了&#xff0c;但是都是特么一拖再拖&#xff0c;刚刚明明是在看TPO阅读的&#xff0c;但是特么实在是无法集中精神的看&#xff0c;作…

VS.NET版本与VC版本对应关系

vc6 -> vc6vs2003 -> vc7vs2005 -> vc8vs2008 -> vc9vs2010 -> vc10vs2012 -> vc11vs2013 -> vc12仅供参考&#xff01;

sql2008 获取表结构说明

SELECT 表名 case when a.colorder1 then d.name else end, 表说明 case when a.colorder1 then isnull(f.value,) else end, 字段序号 a.colorder, 字段名 a.name, 标识 case when COLUMNPROPERTY( a.id,a.name,IsIdentity)1 th…

Linux ALSA声卡驱动之四:Control设备的创建

声明&#xff1a;本博内容均由http://blog.csdn.net/droidphone原创&#xff0c;转载请注明出处&#xff0c;谢谢&#xff01; Control接口 Control接口主要让用户空间的应用程序&#xff08;alsa-lib&#xff09;可以访问和控制音频codec芯片中的多路开关&#xff0c;滑动控件…

【linux】信号量的值定义

参见文件&#xff1a;/usr/include/bits/signum.h /* Signal number definitions. Linux version.Copyright (C) 1995-2013 Free Software Foundation, Inc.This file is part of the GNU C Library.The GNU C Library is free software; you can redistribute it and/ormodi…

汇编学习笔记-序章

最近突然对汇编语言开始感兴趣&#xff0c;于是说干就干了。 之前也自学过一点汇编&#xff0c;是跟着王爽老师的《汇编语言(第3版) 》这本书学习的&#xff0c;已经是有5 6前年的样子了。当时觉得这本书写的非常通俗易懂是一本非常好的启蒙书籍&#xff0c;但是最近在翻阅的时…

jQuery 入门教程(5): 显示/隐藏内容

2019独角兽企业重金招聘Python工程师标准>>> jQuery的hide()和show()可以用来显示和隐藏内容。比如下面的例子&#xff1a;jQuery的hide()和show() 可以用来显示和隐藏内容。比如下面的例子&#xff1a; [html] view plain copy print ? <!doctype html> …

键盘键值表

键盘键值表 值 描述 0x1 鼠标左键 0x2 鼠标右键 0x3 CANCEL 键 0x4 鼠标中键 0x8 BACKSPACE 键 0x9 TAB 键 0xC CLEAR 键 0xD ENTER 键 0x10 SHIFT 键 0x11 CTRL 键 0x12 MENU 键 0x13 PAUSE 键 0x14 CAPS LOCK 键 0x1B ESC 键 0x20 SPACEBAR 键 0x21 PAGE UP 键 0x22 PAGE DOW…

Django QuerySet API文档

在查询时发生了什么(When QuerySets are evaluated) QuerySet 可以被构造&#xff0c;过滤&#xff0c;切片&#xff0c;做为参数传递&#xff0c;这些行为都不会对数据库进行操作。只要你查询的时候才真正的操作数据库。 下面的 QuerySet 行为会导致执行查询的操作&#xff1a…

Spring自动扫描配置及使用方法

2019独角兽企业重金招聘Python工程师标准>>> 首先&#xff0c;检查一下你lib下有没有 common-annotations.jar 这个jar包 没有的话要导入工程。 下一步配置spring的配置文件applicationContex.xml&#xff0c;加入命名空间 红色为需要添加的内容 <beans xmlns…

Linux下ln命令使用

n是linux中又一个非常重要命令&#xff0c;它的功能是为某一个文件在另外一个位置建立一个同步的链接.当我们需要在不同的目录&#xff0c;用到相同的文件时&#xff0c;我们不需要在每一个需要的目录下都放一个必须相同的文件&#xff0c;我们只要在某个固定的目录&#xff0c…

6面向对象的程序设计

ECMA_262把对象定义为&#xff1a;无序属性的集合&#xff0c;其属性可以包含基本值、对象或者函数。 6.1理解对象 特性&#xff08;attribute&#xff09;是内部值&#xff0c;描述了属性&#xff08;property&#xff09;的各种特性。ECMAScript中有两种属性&#xff1a;数据…

DPM 2012 SP1---安装并部署DPM 2012 SP1服务器

实验拓扑图&#xff1a;一、前提条件&#xff1a;需要在DPM2012 SP1服务器上完成以下操作&#xff1a;1.DPM服务器加入域&#xff08;使用域管理员登陆DPM2012 SP1服务器&#xff09;2.准备存储磁盘&#xff08;新添加一块硬盘&#xff09;3.关闭防火墙服务&#xff08;DPM服务…