1.线程的概念:
线程和进程有一定的相似性,通常称为轻量级的进程
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程都有自身控制流
(它自己的指令计数器和cpu时钟)和各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。 一个进程可以有很多线程,每条线程并行执行不同的任务。
线程可以提高应用程序在多核环境下处理诸如文件I/O或者socket I/O等会产生堵塞的情况的表现性能。在Unix系统中,一个进程包含很多东西,包括可执行程序以及一大堆的诸如文件描述符地址空间等资源。在很多情况下,完成相关任务的不同代码间需要交换数据。如果采用多进程的方式,那么通信就需要在用户空间和内核空间进行频繁的切换,开销很大。但是如果使用多线程的方式,因为可以使用共享的全局变量,所以线程间的通信(数据交换)变得非常高效
下图很明白的表示了进程和线程在资源和控制流的差别
按照教科书上的定义,进程是资源管理的最小单位,线程是程序执行的最小单位。在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销。
无论按照怎样的分法,一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。一个进程当然可以拥有多个线程,此时,如果进程运行在SMP机器上,它就可以同时使用多个cpu来执行各个线程,达到最大程度的并行,以提高效率;同时,即使是在单cpu的机器上,采用多线程模型来设计程序,正如当年采用多进程模型代替单进程模型一样,使设计更简洁、功能更完备,程序的执行效率也更高,例如采用多个线程响应多个输入,而此时多线程模型所实现的功能实际上也可以用多进程模型来实现,而与后者相比,线程的上下文切换开销就比进程要小多了,从语义上来说,同时响应多个输入这样的功能,实际上就是共享了除cpu以外的所有资源的。
线程的函数调用一般成功返回0,失败返回非0
2.线程的操作
主要的三个问题:
线程的创建
线程的终止
线程的同步互斥
(1)线程的创建
若是你的linux没有线程相关的man文档,安装在线man帮助:
sudo apt-get install manpages-posix manpages-posix-dev
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); //四个参数意义 pthread_t *thread: 用来得到所创建的线程的ID,所以要创建变量,传入地址。 pthread_attr_t *attr: 线程的属性结构体变量地址,如果不特别指定属性,可以使用默认属性NULL。 start_routine: 线程函数,线程函数一般一个不退出的死循环。 arg:这是Linux系统在调用线程函数时传入的参数。 Compile and link with -pthread.
(2).线程的终止
linux man 文档原文,原文文档说的很明白
The new thread terminates(终止) in one of the following ways: * It calls pthread_exit(3), specifying(指定) an exit status value that is available to another thread in the same process that calls pthread_join(3). * It returns from start_routine(). This is equivalent(等效) to calling pthread_exit(3) with the value supplied in the return statement. * It is canceled (see pthread_cancel(3)). * Any of the threads in the process calls exit(3), or the main thread performs a return from main(). This causes the termination of all threads in the process.
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
从线程函数 return 。这种方法对主线程不适用,从 main 函数 return 相当于调用 exit 。
一个线程可以调用 pthread_cancel 终止同一进程中的另一个线程。
线程可以调用 pthread_exit 终止自己。
(3).线程的属性(创建进程的第二个参数)
The attr argument points to a pthread_attr_t structure whose contents
are used at thread creation time to determine attributes for the new
thread; this structure is initialized using pthread_attr_init(3) and
related functions. If attr is NULL, then the thread is created with
default attributes.
设置线程的分离属性
(1).pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
(2).pthread_detach(pthread_self())
(4)线程的互斥锁
由于线程共享进程的资源和地址空间,在对这些资源进行操作时,就必须要考虑到线程间资源访问的同步与互斥问题
有两种机制:互斥锁和信号量,互斥锁适用于可用资源是唯一的情况,信号量适用于可用资源为多个的情况
互斥锁的基本函数:
1、pthread_mutex_init():互斥锁初始化
2、pthread_mutex_lock():互斥锁上锁
3、pthread_mutex_trylock():互斥锁判断上锁
pthread_mutex_trylock()调用在参数mutex指定的mutex对象当前被锁住的时候立即返回,除此之外,pthread_mutex_trylock()跟pthread_mutex_lock()功能完全一样。
4、pthread_mutex_unlock():互斥锁解锁
5、pthread_mutex_destory():消除互斥锁
- 在主线程中初始化锁为解锁状态
- pthread_mutex_t mutex;
- pthread_mutex_init(&mutex, NULL);
- 在编译时初始化锁为解锁状态
- 锁初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- 访问对象时的加锁操作与解锁操作
- 加锁 pthread_mutex_lock(&mutex)
- 释放锁 pthread_mutex_unlock(&mutex)
- 锁保护的并不是我们的共享变量(或者说是共享内存),对于共享的内存而言,用户是无法直接对其保护的,因为那是物理内存,无法阻止其他程序的代码访问。事实上,锁之所以对关键区域进行了保护,在本例中,是因为所有线程都遵循了一个规则,那就是在进入关键区域钱加
同一把
锁,在退出关键区域钱释放同一把
锁
#include <stdio.h> #include <stdlib.h> #include <pthread.h> int sharedi = 0; void increse_num(void); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int main(){ int ret; pthread_t thrd1, thrd2, thrd3; ret = pthread_create(&thrd1, NULL, (void *)increse_num, NULL); ret = pthread_create(&thrd2, NULL, (void *)increse_num, NULL); ret = pthread_create(&thrd3, NULL, (void *)increse_num, NULL); pthread_join(thrd1, NULL); pthread_join(thrd2, NULL); pthread_join(thrd3, NULL); printf("sharedi = %d\n", sharedi); return 0; } void increse_num(void) { long i,tmp; for(i=0; i<=100000; i++) { /*加锁*/ if (pthread_mutex_lock(&mutex) != 0) { perror("pthread_mutex_lock"); exit(EXIT_FAILURE); } tmp = sharedi; tmp = tmp + 1; sharedi = tmp; /*解锁锁*/ if (pthread_mutex_unlock(&mutex) != 0) { perror("pthread_mutex_unlock"); exit(EXIT_FAILURE); } } }
====================
来自《高级unix编程》的一个互斥锁的一个问题
大概是说在线程的调用函数里,当调用解锁函数pthread_mutex_unlock(&mutex)失败了,将会导致线程的退出,而互斥锁将被锁定了。
而解决办法是用函数把解锁和加锁过程写入一个函数进行封装,然后线程调用函数对该封装函数进行调用。
//信号量是转自别人的,链接会在下文给出
(5).线程信号量
锁有一个很明显的缺点,那就是它只有两种状态
:锁定与不锁定。
信号量本质上是一个非负数的整数计数器,它也被用来控制对公共资源的访问。当公共资源增加的时候,调用信号量增加函数sem_post()对其进行增加,当公共资源减少的时候,调用函数sem_wait()来减少信号量。其实,我们是可以把锁当作一个0-1信号量的。
它们是在/usr/include/semaphore.h
中进行定义的,信号量的数据结构为sem_t, 本质上,它是一个long型整数
相关函数
在使用semaphore之前,我们需要先引入头文件#include <semaphore.h>
- 初始化信号量:
int sem_init(sem_t *sem, int pshared, unsigned int value);
- 成功返回0,失败返回-1
- 参数
- sem:指向信号量结构的一个指针
- pshared: 不是0的时候,该信号量在进程间共享,否则只能为当前进程的所有线程们共享
- value:信号量的初始值
- 信号量减1操作,当sem=0的时候该函数会堵塞
int sem_wait(sem_t *sem);
- 成功返回0,失败返回-1
- 参数
- sem:指向信号量的一个指针
- 信号量加1操作
int sem_post(sem_t *sem);
- 参数与返回同上
- 销毁信号量
int sem_destroy(sem_t *sem);
- 参数与返回同上
-
/************************************************************************* > File Name: sem.c > Author: couldtt(fyby) > Mail: [email protected] > Created Time: 2013年12月15日 星期日 19时25分08秒 ************************************************************************/ #include <stdio.h> #include <unistd.h> #include <pthread.h> #include <semaphore.h> #define MAXSIZE 10 int stack[MAXSIZE]; int size = 0; sem_t sem; // 生产者 void provide_data(void) { int i; for (i=0; i< MAXSIZE; i++) { stack[i] = i; sem_post(&sem); //为信号量加1 } } // 消费者 void handle_data(void) { int i; while((i = size++) < MAXSIZE) { sem_wait(&sem); printf("乘法: %d X %d = %d\n", stack[i], stack[i], stack[i]*stack[i]); sleep(1); } } int main(void) { pthread_t provider, handler; sem_init(&sem, 0, 0); //信号量初始化 pthread_create(&provider, NULL, (void *)handle_data, NULL); pthread_create(&handler, NULL, (void *)provide_data, NULL); pthread_join(provider, NULL); pthread_join(handler, NULL); sem_destroy(&sem); //销毁信号量 return 0; }
- 线程的系统调用
1.pthread_create
NAME
pthread_create - create a new thread
SYNOPSIS
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.
2.pthread_exit(3)
3.pthread_join(3)
4.pthread_cancel(3)
5.pthread_attr_init(3)//属性初始化函数
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
Compile and link with -pthread.
6.pthread_detach()//设置线程分离属性
7.pthread_self()
参考资料:
【1】http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
【2】http://www.cnblogs.com/fuyunbiyi/p/3475602.html
【3】《高级unix编程》
linux线程实现机制:https://www.ibm.com/developerworks/cn/linux/kernel/l-thread/