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

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
plain
 copy

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

编译

[cpp] view
plain
 copy

  1. fs@ubuntu:~/qiang/thread/0107$ gcc -o thread thread.c -lpthread
  2. fs@ubuntu:~/qiang/thread/0107$

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

执行结果如下:

[cpp] view
plain
 copy

  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
plain
 copy

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

执行结果如下:

[cpp] view
plain
 copy

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

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

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

[cpp] view
plain
 copy

  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
plain
 copy

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

V(S)含义如下:

[cpp] view
plain
 copy

  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
plain
 copy

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

执行结果如下:

[cpp] view
plain
 copy

  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$

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

Linux 系统应用编程——线程基础的更多相关文章

  1. Linux 系统应用编程——进程基础

    一.Linux下多任务机制的介绍 Linux有一特性是多任务,多任务处理是指用户可以在同一时间内运行多个应用程序,每个正在执行的应用程序被称为一个任务. 多任务操作系统使用某种调度(shedule)策 ...

  2. Linux系统常用升级的基础包

    Linux系统常用升级的基础包 yum -y install lrzsz gcc gcc-c++ make flex autoconf automake vixie-cron libjpeg libj ...

  3. java并发编程 线程基础

    java并发编程 线程基础 1. java中的多线程 java是天生多线程的,可以通过启动一个main方法,查看main方法启动的同时有多少线程同时启动 public class OnlyMain { ...

  4. Linux系统shell编程自学_第一章基础

    第一章 基础shell的优势在于处理操作系统底层的业务,Python,php的优势在于开发运维工具,web界面的管理工具以及web业务开发.处理一键安装.优化.报警脚本shell又叫命令解释器,它能识 ...

  5. Linux系统文件系统及文件基础篇

    学习Linux,重难点在于掌握不同类别的文件系统及其作用.通过对Linux系统的安装,我们首先来了解下Linux系统里各个目录文件夹下的大致功能:主要的目录树的有/./root./home./usr. ...

  6. linux系统串口编程实例

    在嵌入式开发中一些设备如WiFi.蓝牙......都会通过串口进行主机与从机间通信,串口一般以每次1bit位进行传输,效率相对慢. 在linux系统下串口的编程有如下几个步骤,最主要的是串口初始化! ...

  7. windows核心编程 - 线程基础

    一.基本概念: 一个进程至少需要一个线程. 组成:一个线程包括仅包括一个线程堆栈和一个线程内核对象 线程堆栈:用于维护线程在执行代码时需要的所有函数参数和局部变量 线程内核对象:操作系统用它来对线程实 ...

  8. linux makefile: c++ 编程_基础入门_如何开始?

    学习android 终究还是需要研究一下其底层框架,所以,学习c++很有必要. 这篇博客,算是linux(ubuntu) 下学习 c++ 的一个入门. 刚开始学习编程语言的时候,最好还是使用命令行操作 ...

  9. 《linux系统及其编程》实验课记录(五)

    实验 5:权限的设置和更改 实验环境: 安装了 Red Hat Enterprise Linux 6.0 可运行系统,并且是成功验证系统.有另外一个无特权用户 student,密码 student 的 ...

随机推荐

  1. MPI二维笛卡尔坐标划分【1】

    本文简单演示,如何对现有进程进行二维划分,如何获得进程的X和Y坐标. 只有一段程序: #include <mpi.h> #include <stdio.h> #include ...

  2. Android简易实战教程--第十四话《模仿金山助手创建桌面Widget小部件》

    打开谷歌api,对widget小部件做如下说明: App Widgets are miniature application views that can be embedded in otherap ...

  3. Oracle AP Invoice APIs

     These APIs are handful when you want to do Insert, Update or Delete programmatically for some bus ...

  4. 关于GCJ02和WGS84坐标系的一点实验

    大家都知道,在兲朝的电子地图的坐标都是经过了一个坐标偏移,叫GCJ_02的东西.在网上发现了将WGS84经纬度转成GCJ02的一个代码,写了个小程序测试了下看看全国各地的偏移量有多大. 关于WGS84 ...

  5. arm-linux内核编译过程小结

    记在前面的杂七杂八 内核的生成,实际上最终的目的是生成一个binary文件zImage,大小2-5MB的数量级. 用户可以从kernel.org得到的tar.gz格式的内核源代码,此代码解压后,就会生 ...

  6. shell入门之函数应用

    最近在学习shell编程,文中若有错误的地方还望各位批评指正. 先来看一个简单的求和函数 #!/bin/bash #a test about function f_sum 7 8 function f ...

  7. Android:ADB server didn't ACK或者adb server is out of date. killing解决办法

    欢迎关注公众号,每天推送Android技术文章,二维码如下:(可扫描) 出现这个原因我个人感觉有两个.一.5037端口被别的程序或者进程占用:二.adb占用的不是5037端口.很多人仅仅知道第一种二忽 ...

  8. Mysql SQL Mode详解

    Mysql SQL Mode简介 MySQL服务器能够工作在不同的SQL模式下,并能针对不同的客户端以不同的方式应用这些模式.这样,应用程序就能对服务器操作进行量身定制以满足自己的需求.这类模式定义了 ...

  9. [Redmine] Centos5上安装Redmine3.0+nginx+thin部署

    项目管理的需要所以安装Redmine,其实wiki放在上面也不错的. 首先是安装,ruby应用第一次装,把坑记住. nginx, mysql是已经安装好的,只需要配置, 结合nginx的部署方式很多, ...

  10. JSON 的数据转换格式(DataTable或DataSet) -善良公社项目

    这两天在使用JqueryEasyUI框架绑定数据并实现自动分页时,由于框架的限制需要使用Json数据的来传递与获取数据: JSON的全称是JavaScript Object Notation, 是一种 ...