我们知道,进程在各自独立的地址空间中运行,进程之间共享数据需要用mmap或者进程间通信机制,有些情况需要在一个进程中同时执行多个控制流程,这时候线程就派上了用场,比如实现一个图形界面的下载软件,一方面需要和用户交互,等待和处理用户的鼠标键盘事件,另一方面又需要同时下载多个文件,等待和处理从多个网络主机发来的数据,这些任务都需要一个“等待-处理”的循环,可以用多线程实现,一个线程专门负责与用户交互,另外几个线程每个线程负责和一个网络主机通信。信号处理函数是同一个进程地址空间中的多个控制流程,多线程也是如此,但是比信号处理函数更加灵活,信号处理函数的控制流程只是在信号递达时产生,在处理完信号之后就结束,而多线程的控制流程可以长期并存,操作系统会在各线程之间调度和切换,就像在多个进程之间调度和切换一样。由于同一进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表
  • 每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

但有些资源是每个线程各有一份的:

  • 线程id
  • 上下文,包括各种寄存器的值、程序计数器和栈指针
  • 栈空间
  • errno变量
  • 信号屏蔽字
  • 调度优先级

1.创建线程

include <pthread.h>

int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void*), void *restrict arg);

返回值:成功返回0,失败返回错误号。以前学过的系统函数都是成功返回0,失败返回-1,而错误号保存在全局变量errno中,而pthread库的函数都是通过返回值返回错误号,虽然每个线程也都有一个errno,但这是为了兼容其它函数接口而提供的,pthread库本身并不使用它,通过返回值返回错误码更加清晰。在一个线程中pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给

pthread_create的函数指针start_routine决定。

2.终止线程

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

  • 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  • 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。
  • 线程可以调用pthread_exit终止自己。

pthread_cancel

  #include <pthread.h>
  int pthread_cancel(pthread_t thread);
  int pthread_setcancelstate(int state, int *oldstate);
  int pthread_setcanceltype(int type, int *oldtype);
  void pthread_testcancel(void);
   Cancellation是一种一个线程可以结束另一个线程执行的机制。更确切的说,一个线程可以发生Cancellation请求给另一个线程。根据线程的设置,收到请求的线程可以忽视这个请求,立即执行这个请求或者延迟到一个cancellation点执行。当一个线程执行Cancellation请求,相当于在那个点执行pthread_exit操作退出:所有cleanup句柄被反向调用,所有析构函数被调用结束线程并返 回pthread_canceled.
pthread_exit
   
#include <pthread.h>   void pthread_exit(void *retval);
   pthread_exit结束调用线程的执行.所有通过pthread_cleanup_push设置的清除句柄将会被反序执行(后进先出)。所以key值非空的线程特定数据Finalization functions被调用(参见pthread_key_create)。最后调用线程被终止。  retval是这个线程结束的返回值,可以通过在别的线程中调用pthread_join来获取这个值。
   没有返回值。
 
 pthread_join 
   #include <pthread.h>
   int pthread_join(pthread_t th, void **thread_return);    
   挂载一个在执行的线程直到该线程通过调用pthread_exit或者cancelled结束。如果thread_return不为空,则线程th的返回值会保存到thread_return所指的区域。th的返回值是它给pthread_exit的参数,或者是pthread_canceled 如果是被cancelled的。 被依附的线程th必须是joinable状态。一定不能是detached通过使用pthread_detach或者pthread_create中使用pthread_create_detached属性。当一个joinable线程结束时,他的资源(线程描述符和堆栈)不会被释放直到另一个线程对它执行pthread_join操作。

如果成功,返回值存储在thread_return中,并返回0,否则返回错误码:
   ESRCH:找不到指定线程
   EINVAL:线程th是detached或者已经存在另一个线程在等待线程th结束
   EDEADLK:th的参数引用它自己(即线程不能join自身)

 

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

#include <pthread.h>

int pthread_detach(pthread_t tid);

返回值:成功返回0,失败返回错误号

以下为一个例子:

程序执行结果:

3.线程间同步

对于多线程的程序,访问冲突的问题是很普遍的,解决的办法是引入互斥锁(Mutex,Mutual Exclusive Lock),获得锁的线程可以完成“读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而不能访问共享数据,这样“读-修改-写”三步操作组成一个原子操作,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作。

Mutex用pthread_mutex_t类型的变量表示,可以这样初始化和销毁:

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

返回值:成功返回0,失败返回错误号。

pthread_mutex_init函数对Mutex做初始化,参数attr设定Mutex的属性,如果attr为NULL则表示缺省属性,Mutex属性以后再讲。用pthread_mutex_init函数初始化的Mutex可以用pthread_mutex_destroy销毁。如果Mutex变量是静态分配的(全局变量或static变量),也可以用宏定PTHREAD_MUTEX_INITIALIZER来初始化,相当于用pthread_mutex_init初始化并且attr参数为NULL。Mutex的加锁和解锁操作可以用下

列函数:

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

返回值:成功返回0,失败返回错误号。

一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用pthread_mutex_lock获得了该 Mutex,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该 Mutex并继续执行。如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。

以下为程序例子:

4.信号量控制线程的互斥与同步

信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这 个资源。只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait使得线程阻塞,直到sem_post释放后value值加一,但是sem_wait返回之前还是会将此value值减一.

#include <semaphore.h>
 
 int sem_init(sem_t *sem, int pshared, unsigned int value);
 初始化信号量,pshared表示该信号量是否只属于当前进程(pshared==0),如果pshared不为0则表示可以在进程间共享。value表示该信号量的初始值。
 
 int sem_wait(sem_t * sem);
如果信号量的值大于0,则自动减1并立即返回。否则线程挂起直到sem_post或sem_post_multiple增加信号量的值
 
 
 int sem_timedwait(sem_t * sem, const struct timespec *abstime);
同sem_wait,只是多了个超时设置,如果超时,首先将全局变量errno设为ETIMEDOUT,然后返回-1.
 
 
 int sem_trywait(sem_t * sem);
如果信号量的值大于0,将信号量减1并立即返回,否则将全局变量errno设为ETIMEDOUT,然后返回-1。sem_trywait不会阻塞。
 
 
 int sem_post(sem_t * sem);
释放一个正在等待该信号量的线程,或者将该信号量+1.
 
 
 int sem_post_multiple(sem_t * sem, int number);
释放多个正在等待的线程。如果当前等待的线程数n小于number,则释放n个线程,并且该信号量的值增加(number - n).
 
 
 int sem_getvalue(sem_t * sem, int * sval);
返回信号量的值
 
 
int sem_destroy(sem_t * sem);
用来释放信号量
 
以下为程序例子:

linux编程之多线程编程的更多相关文章

  1. 【转】 Linux下的多线程编程

    作者:gnuhpc 出处:http://www.cnblogs.com/gnuhpc/原文链接:http://www.cnblogs.com/gnuhpc/archive/2012/12/07/280 ...

  2. Linux下的多线程编程

    1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的 Unix也支持线程的概念,但是在一个进程(proces ...

  3. 【转】Linux下的多线程编程

    1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的 Unix也支持线程的概念,但是在一个进程(proces ...

  4. 《转》Linux下的多线程编程

    原地址:http://linux.chinaunix.net/doc/program/2001-08-11/642.shtml 1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程 ...

  5. 每天进步一点点——论fork()函数与Linux中的多线程编程

    转载请说明出处:http://blog.csdn.net/cywosp/article/details/27316803 一.fork()函数     在操作系统的基本概念中进程是程序的一次运行,且是 ...

  6. 【转】Linux下的多线程编程背景知识

    1. 进程和线程 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的 Unix也支持线程的概念,但是在一个进程(pr ...

  7. Linux C++的多线程编程(转)

    1. 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的Unix也支持线程的概念,但是在一个进程(proces ...

  8. Linux C语言多线程编程实例解析

    Linux系统下的多线程遵循POSIX线程接口,称为 pthread.编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a.顺便说一下,Linux ...

  9. SDK编程之多线程编程

    本课中,我们将学习如何进行多线程编程.另外我们还将学习如何在不同的线程间进行通信. 理论:前一课中,我们学习了进程,其中讲到每一个进程至少要有一个主线程.这个线程其实是进程执行的一条线索,除此主线程外 ...

  10. Java多线程编程(2)--多线程编程中的挑战

    一.串行.并发和并行   为了更清楚地解释这三个概念,我们来举一个例子.假设我们有A.B.C三项工作要做,那么我们有以下三种方式来完成这些工作:   第一种方式,先开始做工作A,完成之后再开始做工作B ...

随机推荐

  1. AndroidStudio3.0 注解报错Annotation processors must be explicitly declared now. The following dependencies on the compile classpath are found to contain annotation processor.

    把Androidstudio2.2的项目放到3.0里面去了,然后开始报错了. 体验最新版AndroidStudio3.0 Canary 8的时候,发现之前项目的butter knife报错,用到注解的 ...

  2. delphi怎样单步调试

    在delphi的IDE编辑窗口里,主菜单->Run->Step Over或者主菜单->Run->Trace Into单步调试有两种方式:一种是Step Over,快捷键是F8, ...

  3. JAVA的泛型与反射的联合应用

    通过泛型与反射的结合,可以编写框架来使开发更容易,这里演示的是BaseDao部分的简单使用. BaseDao部分代码: public abstract class BaseDao<T>{ ...

  4. Go语言【第六篇】:Go循环语句

    Go语言循环语句 在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句,以下为大多数编程语言循环程序的流程如: Go语言提供了以下几种类型循环处理语句: 循环类型 描述 fo ...

  5. NOIP2018初赛 游记

    这玩意写个鬼游记啊 听说普及+提高的考两张卷子€€£也是想得出来 怎么监考还能咕咕咕的啊 怎么我到快结束了才做完啊 怎么我根本不知道初赛能带啥啊 怎么dij我都能想着对的选了错的啊 怎么我根本不知道图 ...

  6. 《转》'autocomplete="off"'在Chrome中不起作用解决方案

    最近项目中遇到一个令人头疼的问题,查阅各种资料,尝试各种方法,最终得以解决:哎···下面就说说这心酸的历程吧. 大家都知道autocomplete属性是表单字段中的HTML5新属性,该属性有两种状态值 ...

  7. BZOJ4872:[SHOI2017]分手是祝愿——题解

    http://www.lydsy.com/JudgeOnline/problem.php?id=4872 https://www.luogu.org/problemnew/show/P3750 Zei ...

  8. mysql语句及执行计划

    数据库链接: mysql -uroot -p <!--数据库连接-->mysql -h10.0.0.100 -uuser -passwordshow databases <!--查看 ...

  9. 【DP】【Uva437】UVA437 The Tower of Babylon

    传送门 Description Input Output Sample Input Sample Output Case : maximum height = Case : maximum heigh ...

  10. Ultra-QuickSort POJ - 2299 树状数组求逆序对

    In this problem, you have to analyze a particular sorting algorithm. The algorithm processes a seque ...