我们知道,进程在各自独立的地址空间中运行,进程之间共享数据需要用进程间通信机制,有些情况需要在一个进程中同时执行多个控制流程,这时候线程就派上了用场,比如实现一个图形界面的下载软件,一方面需要和用户交互,等待和处理用户的鼠标键盘事件,另一方面又需要同时下载多个文件,等待和处理从多个网络主机发来的数据,这些任务都需要一个“等待-处理”的循环,可以用多线程实现,一个线程专门负责与用户交互,另外几个线程每个线程负责和一个网络主机通信。

注:linux 2.6 以后的线程就是由用户态的pthread库实现的.使用pthread以后, 在用户看来, 每一个task_struct就对应一个线程, 而一组线程以及它们所共同引用的一组资源就是一个进程.在linux 2.6中, 内核有了线程组的概念, task_struct结构中增加了一个tgid(thread group id)字段. getpid(获取进程ID)系统调用返回的也是tast_struct中的tgid, 而tast_struct中的pid则由gettid系统调用来返回。
     当线程停止/继续, 或者是收到一个致命信号时, 内核会将处理动作施加到整个线程组中。
     比如程序a.out运行时,创建了一个线程。假设主线程的pid是10001、子线程是10002(它们的tgid都是10001)。这时如果你kill 10002,是可以把10001和10002这两个线程一起杀死的,尽管执行ps命令的时候根本看不到10002这个进程。如果你不知道linux线程背后的故事,肯定会觉得非常奇怪。
   与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”开头,要使用这些函数库,要通过引入头文<pthread.h>,而且链接这些线程函数库时要使用编译器命令的“-lpthread”选项[Ubuntu系列系统需要添加的是”-pthread”选项而不是”-lpthread”,如Ubuntu 14.04版本,深度Ubuntu等]
下面开始介绍posix线程基本的API:
pthread_create
int pthread_create(pthread_t *restrict thread,  const pthread_attr_t *restrict attr,  void *(*start_routine)(void*), void *restrict arg);  

创建一个新的线程

参数

thread:线程ID

attr:设置线程的属性,一般设置为NULL表示使用默认属性

start_routine:是个函数地址,线程启动后要执行的函数

arg:传给线程启动函数的参数

返回值:成功返回0;失败返回错误码;

以前学过的系统函数都是成功返回0,失败返回-1,而错误号保存在全局变量errno中,而pthread库的函数都是通过返回值返回错误号,虽然每个线程也都有一个errno,但这是为了兼容其它函数接口而提供的,pthread库本身并不使用它,通过返回值返回错误码更加清晰。由于pthread_create的错误码不保存在errno中,因此不能直接用perror(3)打印错误信息,可以先用strerror(3)把错误号转换成错误信息再打印。读取返回值要比读取线程内的errno变量的开销更小!

/** 实践: 新的错误检查与错误退出函数 **/
inline void err_check(const std::string &msg, int retno)
{  if (retno != 0)  err_exit(msg, retno);
}
inline void err_exit(const std::string &msg, int retno)
{  std::cerr << msg << ": " << strerror(retno) << endl;  exit(EXIT_FAILURE);
}  

pthread_exit

void pthread_exit(void *value_ptr); 

value_ptr:value_ptr不要指向一个局部变量,因为当其它线程得到这个返回指针时线程函数已经退出了。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

1、从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit,而如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止。

2、一个线程可以调用pthread_cancel 终止同一进程中的另一个线程。

3、线程可以调用pthread_exit终止自己。

pthread_join

int pthread_join(pthread_t thread, void **value_ptr);  

当pthread_create 中的 start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,类似于父进程调用wait(2)得到子进程的退出状态。

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

1、如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。

2、如果thread线程被别的线程调用pthread_cancel异常终止掉,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED。

3、如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。

如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。

/** 示例: 等待线程退出 **/
void *thread_rotine(void *args)
{  for (int i = 0; i < 10; ++i)  {  printf("B");  fflush(stdout);  usleep(20);  }  pthread_exit(NULL);
}  int main()
{  pthread_t thread;  int ret = pthread_create(&thread, NULL, thread_rotine, NULL);  err_check("pthread_create", ret);  for (int i = 0; i < 10; ++i)  {  printf("A");  fflush(stdout);  usleep(20);  }  ret = pthread_join(thread, NULL);  err_check("pthread_join", ret);  putchar('\n');  return 0;
}  

pthread_self

pthread_t pthread_self(void);  

返回线程ID

在Linux上,pthread_t类型是一个地址值,属于同一进程的多个线程调用getpid(2)可以得到相同的进程号,而调用pthread_self(3)得到的线程号各不相同。线程id只在当前进程中保证是唯一的,在不同的系统中pthread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单地当成整数用printf打印。

/** 示例:主控线程与子线程传递数据 **/
typedef struct _Student
{  char name[20];  unsigned int age;
} Student;  void *threadFunction(void *args)
{  cout << "In Thread: " << pthread_self() << endl;  Student tmp = *(Student *)(args);  cout << "Name: " << tmp.name << endl;  cout << "Age: " << tmp.age << endl;  pthread_exit(NULL);
}  int main()
{  Student student = {"tach",22};  pthread_t thread;  //启动创建并启动线程  pthread_create(&thread,NULL,threadFunction,&student);  //等待线程结束  pthread_join(thread,NULL);  return 0;
}  

pthread_cancel

int pthread_cancel(pthread_t thread);

线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定 。

pthread_detach

int pthread_detach(pthread_t thread);  

一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止(僵线程)。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL。对一个尚未detach的线程调用pthread_join或pthread_detach都可以把该线程置为detach状态,也就是说,不能对同一线程调用两次pthread_join,或者如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。

这个函数既可以在主线程中调用,也可以在thread_function里面调用。

总结:进程 VS. 线程

进程(pid_t)

线程(pthread_t)

Fork

Pthread_create

Waitpit

Pthread_join/Pthread_detach

Kill

Pthread_cancel

Pid

Pthead_self

Exit/return

Pthread_exit/return

僵尸进程(没有调用wait/waitpid等函数)

僵尸线程(没有调用pthread_join/pthread_detach)

/** 将并发echo server改造成多线程形式  **/
void echo_server(int clientSocket);
void *thread_routine(void *arg);
int main()
{  int sockfd = socket(AF_INET,SOCK_STREAM,0);  if (sockfd == -1)  err_exit("socket error");  int optval = 1;  if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)) == -1)  err_exit("setsockopt error");  struct sockaddr_in serverAddr;  serverAddr.sin_family = AF_INET;  serverAddr.sin_port = htons(8002);  serverAddr.sin_addr.s_addr = INADDR_ANY;    //绑定本机的任意一个IP地址  if (bind(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == -1)  err_exit("bind error");  if (listen(sockfd,SOMAXCONN) == -1)  err_exit("listen error");  while (true)  {  int peerSockfd = accept(sockfd, NULL, NULL);  if (peerSockfd == -1)  err_exit("accept error");  pthread_t tid;  /**注意: 下面这种用法可能会产生问题 当另一个连接快读快速到达, peerSockfd的内容更改, 新创建的线程尚未将该值取走时,线程读取的就不是 我们原来想让线程读取的值了 int ret = pthread_create(&tid, NULL, thread_routine, (void *)&peerSockfd); **/  //解决方案: 为每一个链接创建一块内存 ,注意之后要释放 int *p = new int(peerSockfd);  int ret = pthread_create(&tid, NULL, thread_routine, p);  if (ret != 0)  err_thread("pthread_create error", ret);  }  close(sockfd);
} 
void *thread_routine(void *args)
{  //将线程设置分离状态, 避免出现僵尸线程  pthread_detach(pthread_self());  int peerSockfd = *(int *)args;  //注意函数中指针取出之后记得将内存释放掉 delete (int *)args;  echo_server(peerSockfd);  cout << "thread " << pthread_self() << " exiting ..." << endl;  pthread_exit(NULL);
}
void echo_server(int clientSocket)
{  char buf[BUFSIZ] = {0};  int readBytes;  while ((readBytes = read(clientSocket, buf, sizeof(buf))) >= 0)  {  if (readBytes == 0)  {  cerr << "client connect closed" << endl;  break;  }  if (write(clientSocket, buf, readBytes) == -1)  {  cerr << "server thread write error" << endl;  break;  }  cout << buf;  bzero(buf, sizeof(buf));  }
}  

Linux多线程实践(二)线程基本API(POSIX)相关推荐

  1. Linux多线程实践(2) --线程基本API

    POSIX线程库 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以"pthread_"开头,要使用这些函数库,要通过引入头文<pthread.h>,而且链 ...

  2. Linux多线程实践(1) --线程理论

    线程概念 在一个程序里的一个执行路线就叫做线程(thread).更准确的定义是:线程是"一个进程内部的控制序列/指令序列"; 一切进程至少有一个执行线程; 进程  VS. 线程  ...

  3. Linux多线程实践(六)使用Posix条件变量解决生产者消费者问题

    前面的一片文章我们已经讲过使用信号量解决生产者消费者问题.那么什么情况下我们须要引入条件变量呢? 这里借用  http://www.cnblogs.com/ngnetboy/p/3521547.htm ...

  4. Linux多线程实践(4) --线程特定数据

    线程特定数据 int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *)); int pthread_key_ ...

  5. Linux多线程实践(3) --线程属性

    初始化/销毁线程属性 int pthread_attr_init(pthread_attr_t *attr); int pthread_attr_destroy(pthread_attr_t *att ...

  6. linux多线程学习(二)——线程的创建和退出

    在上一篇文章中对线程进行了简单的概述,它在系统中和编程的应用中,扮演的角色是不言而喻的.学习它.掌握它.吃透它是作为一个程序员的必须作为.在接下来的讲述中,所有线程的操作都是用户级的操作.在LINUX ...

  7. linux下多线程 排序,Linux多线程实践(7) --多线程排序对比

    屏障 int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restri ...

  8. Linux多线程实践(7) --多线程排序对比

    屏障 int pthread_barrier_init(pthread_barrier_t *restrict barrier,const pthread_barrierattr_t *restric ...

  9. Linux内核 实践二

    实践二 内核模块编译 20135307 张嘉琪 一.实验原理 Linux模块是一些可以作为独立程序来编译的函数和数据类型的集合.之所以提供模块机制,是因为Linux本身是一个单内核.单内核由于所有内容 ...

最新文章

  1. 简简单单搞掂恼人的Laravel 5安装
  2. 运城学院数学与计算机系,运城学院数学与信息技术学院.doc
  3. 来自技术人的呐喊:回归本心,远离内卷
  4. 贪心——跳跃游戏(Leetcode 55)
  5. My new English
  6. Windows系统下的python程序安装
  7. 识别引擎ocropy-ocropy2-OCRopus3总结
  8. 在macOS Big Sur上如何重置蓝牙?
  9. 公司管理系列--最难挖的阿里,最好挖的百度;最难走的360,最易走的腾讯
  10. mysql存储过程switch_Mysql存储过程从0开始(上)
  11. 高斯-勒让德积分学习
  12. qqkey获取原理_qqkey获取器下载
  13. 1.分布式服务架构:原理、设计与实战 --- 分布式微服务架构设计原理
  14. 如何设置路由器的中继模式-机器人局域网组网攻略
  15. 渲染巨匠(lightscape) 3.2 SP1 汉化版
  16. ORA-12154 另一种解决方式,IIS发布后出现的---解决思路---终极方案
  17. FME 2011预览:新特性 IFMEWorkspaceRunner
  18. Mysql 8.0 安装详细教程、问题处理、卸载(亲测可用)
  19. ubuntu下ffmpeg图片转视频
  20. C++学习(一一七)pdb文件

热门文章

  1. 同济大学计算机贴吧,沪上21所高校新老毕业照大集合!杨浦毕业生们,这里有你们的回忆吗?...
  2. 调用链与日志的关联式跟踪查询
  3. 基于Gradle创建SpringCloud项目
  4. 可扩展的分布式数据库架构
  5. json转换csv的python实现
  6. 谷歌扩展装不上,清单文件缺失或不可读取 无法加载清单。解决方法
  7. 小白必看:一文读懂推荐系统负采样
  8. maya2015 中英文界面切换(语言设置)
  9. STM32F103C8T6定时器实现led的周期闪烁及PWM实现流水灯
  10. C语言-算术运算和赋值运算