1. 什么是线程
在一个程序中的多个执行路线就叫线程。更准确的说:线程就是一个进程内部的一个控制序列。
弄清楚fork系统调用和创建新线程之间的区别非常重要。当进程执行fork调用时,将创建出该进程的一份新副本。这个新进程拥有自己的变量和之间的PID,它的时间调用也是独立的,他们的执行几乎完全独立与父进程。当在进程中创建一个新线程时,新的执行线程将拥有自己的栈,但与它的创建者共享全局变量、文件描述符、信号处理函数和当前目录状态。
2. 线程的优点和缺点
在某些环境下,创建新线程要比创建新进程要有更明显的优势。新线程的创建代价要比新进程小得多。下面是线程的一些优缺点:
优点:
- 有时,让程序看起来好像是在同时做两件事是很有用的。
- 一个混杂者输入、计算和输出的应用程序,可以将这几个部分分离为3个线程来执行。从而改善程序执行的性能。
- 一般而言,线程之间的切换需要操作系统做的工作要比进程之间的切换少得多,因此多个线程多资源的需求要远小于多个进程。
缺点:
- 编写多线程程序需要非常仔细的设计。在多线程程序中,因时序上的细微差别或无意间造成的变量共享二引发错误的可能性是很大的。
- 多线程的调试要比单线程程序调试困难得多。
3. 第一个线程程序
- 绝大数函数名以pthread_开头,并且在编译程序时需要用选项-lpthread来链接程序。
- 在一个多线程程序中,只有一个errno的变量供所有线程共享。在一个线程获取时,也容易被另一个线程改变。类似的问题,还存在与fputs之类的函数。这些函数通常用一个全局性区域来缓冲输出数据。
- 一个概念:可重入代码可以被多次调用而仍然正常工作。
- 函数pthread_create,它的作用是创建一个新线程。原型:
int pthread_create(pthread_t * thread,pthread_attr_t * attr,void *(*start_routine)(void * ),void * arg)
- arg1:这个指针指向的变量将被写入一个标识符,使用这个标识符来引用新线程
- arg2:线程的属性,一般不需要设置,直接赋值为NULL
- arg3和arg4:线程要启动执行的函数即传递给函数的参数。
- 线程通过pthread_exit函数终止执行。
- 函数原型:
void pthread_exit(void *retval);
4. 信号量
信号量是一个特殊类型的变量,它可以被增加或被减少,但操作上被保证是原子性的操作。即同时只有一个线程去操作它。通过信号量的计数来保证任一时刻只有一个线程可以访问被它包含的共享资源。
- 函数sem_init用于初始化一个信号量。
- 原型:
int sem_init(sem_t *sem, int pshared, unsigned int value);
这个函数初始化sem指向的信号量对象,pshared参数控制信号量的类型,value参数是信号量的值。如果这个值小于0则访问它的值将被堵塞。 int sem_wait(sem_t *sem);
和int sem_post(sem_t *sem);
两个函数用于控制信号量的值。这两个函数都以初始化后的信号量对象的指针为参数。前者将以原子操作的方式将信号量间1,但它只有等待信号量为非零值才会这么做。后者同样以原子操作的方式给信号量的值加1- 最后一个
int sem_destroy(sem_t *sem)
,信号量的删除函数。如果试图删除的信号量正在被其它一些线程等待,就会收到一个错误。 - 与大多Linux函数一样,这些函数的成功返回值都为0,并且会设置errno的变量
5. 互斥量
另外一种在多线程中同步访问的方法是使用互斥量。它允许程序员锁住某个对象,使得每次只能有一个线程访问它。为了控制对某个共享资源的访问,必须在操作资源的代码之前锁住一个互斥量,在完成后一定要解锁这个互斥量。
函数如下,类似与信号量:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
函数调用成功返回0,失败返回错误码,但未设置errno。注意在一个线程中对一个互斥量进行两次或以上的加锁操作,这样在本线程中,不能解锁。其它线程也因无法加锁互斥量而堵塞,遂而形成死锁。
6. 线程属性
线程可以被脱离,调度,取消。通过设置线程的属性可以做到这一点。
7. 多线程
当我们一次性使用循环的方式创建一个数组的线程。在pthread_create函数中的最后一个选项中,将数组的下标作为参数传递到子线程中当参数时。由于主线程的运行足够快,就可能会改变某些线程的参数对共享变量和多个执行路径没有做到足够重视,程序就有可能出现错误。
错误为,当主线程不断的执行pthread_create函数,在子线程中打印传进来的参数就发生了不一致的变化。以下为一个简单的解决方法。
for (lots_of_threads = 0;lots_of_threads < NUM_THREAD; lots_of_threads++){
//使用fakelock(全局变量)来查看子线程是否创建完成
while(!fakelock) {
}
pthread_mutex_lock(&mutex);
fakelock = 0;
pthread_mutex_unlock(&mutex);
res = pthread_create(&(a_thread[lots_of_threads]),NULL,thread_function,&lots_of_threads);
if (res != 0){
char * str;
sprintf(str,"[%d] pthread_create failure",lots_of_thr:eads);
perror(str);
exit(EXIT_FAILURE);
}
}
错误的情况,如下
thread_function is running. Argument was 3
thread_function is running. Argument was 5
thread_function is running. Argument was 2
Waiting for threads to finish...
thread_function is running. Argument was 1
thread_function is running. Argument was 4
改善后的情况
hread[1] is running
Bye Form 1
thread[2] is running
Bye Form 2
thread[3] is running
Bye Form 3
thread[4] is running
Bye Form 4
thread[5] is running
Bye Form 5
Waiting for threads to finish...
thread[6] is running
Bye Form 6
All done