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

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. 带你深入理解STL之迭代器和Traits技法

    在开始讲迭代器之前,先列举几个例子,由浅入深的来理解一下为什么要设计迭代器. //对于int类的求和函数 int sum(int *a , int n) { int sum = 0 ; for (in ...

  2. Intent和PendingIntent的区别

    intent英文意思是意图,pending表示即将发生或来临的事情.  PendingIntent这个类用于处理即将发生的事情.比如在通知Notification中用于跳转页面,但不是马上跳转.  I ...

  3. LiveBlox无需代码的开发工具--支持win macos ubuntu等开发环境--

    LiveBlox无需代码的开发工具-支持windows macos ubuntu. 强大 灵活 易于使用 视频简介:LiveBlox Develop Technology Without Coding ...

  4. (七十九)MapKit的基本使用

    MapKit是苹果公司开发的用于显示地图和实现定位.导航的地图框架. MapKit View可以通过storyboard.xib创建,也可以通过代码直接创建. 需要注意的是,通过storyboard和 ...

  5. 用SpriteBuilder简化"耕牛遍地走"的动画效果(四)

    写到这突然有童鞋质疑,你这哪里是牛,分明是熊嘛! 仔细看了下,还真像牛.反正是这个意思.怪本猫猪牛熊不分,好在道理是一样的. 下面继续,言归正传. 添加一个空白的touchBegan方法,如果没有这个 ...

  6. 今日成为CSDN认证专家

    认证时写的申请材料: 程序猿一枚毕业于南开工作于上海.喜欢读书,喜欢跑步,激情似火,心静如水. 喜欢编程,喜欢寻根问底各种技术,喜欢在各种新技术中汲取营养. 喜欢分享,因此以一些高质量的博文来回报各位 ...

  7. Chapter 2 User Authentication, Authorization, and Security(1):选择Windows和SQL 身份验证

    原文出处:http://blog.csdn.net/dba_huangzj/article/details/38657111,专题目录:http://blog.csdn.net/dba_huangzj ...

  8. 使用批处理文件(*.bat)同时打多个cmd窗口

    使用批处理文件(*.bat)同时打多个cmd窗口 最近在研究zookeeper,在本地建了几个目录,发现频繁的去各个目录启动zkServer.cmd十分繁琐,于是乎google,才有了下文: 使用批处 ...

  9. protobuf代码生成

    windows : 1,两个文件:proto.exe, protobuf-java-2.4.1.jar 2,建立一个工程TestPb,在下面建立一个proto文件件,用来存放[.proto]文件 3, ...

  10. Socket编程实践(9) --套接字IO超时设置方法

    引:超时设置3种方案 1. alarm超时设置方法 //代码实现: 这种方式较少用 void sigHandlerForSigAlrm(int signo) { return ; } signal(S ...