多线程的最大优点之一是数据的共享性,各个进程共享父进程处沿袭的数据段,可以方便的获得、修改数据。但这也给多线程编程带来了许多问题。 我们必须当心有多个不同的进程访问相同的变量。许多函数是不可重入的,即同时不能运行一个函数的多个拷贝(除非使用不同的数据段)。 在函数中声明的静态变量常常带来问题,函数的返回值也会有问题。因为如果返回的是函数内部静态声明的空间的地址, 则在一个线程调用该函数得到地址后使用该地址指向的数据时,别的线程可能调用此函数并修改了这一段数据。 为了保护变量,我们必须使用信号量、互斥等方法来保证我们对变量的正确使用。在Poxi标准中提供了互斥量,读写锁,条件变量实现互斥访问,下面意义探讨。
假设多个线程向同一个文件写入数据,若对写入的顺序不加以管理和控制,那么最终生成的文件肯定是无法解析的。所以必须用互斥锁来保证一段时间内只有一个线程在写入文件,当一个线程写入完成后再给下一线程写入。
锁可以被动态或静态创建,可以用宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁,采用这种方式比较容易理解,互斥锁是pthread_mutex_t的结构体,而这个宏是一个结构常量,如下可以完成静态的初始化:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;另外锁可以用pthread_mutex_init函数动态的创建,函数原型如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)NULL参数表明使用默认属性,通常使用默认属性。
pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。
当锁没有被锁定时。可以通过调用pthread_mutex_destory释放锁占用的资源。
多个线程可以同时获得读锁(Reader-Writer lock in read mode),但是只有一个线程能够获得写锁(Reader-writer lock in write mode)。
读写锁总共有三种状态: 1. 一个或者多个线程获得读锁,其他线程无法获得写锁 2. 一个线程获得写锁,其他线程无法获得读锁 3. 没有线程获得此读写锁
#include <pthread.h>//初始化一个读写锁,pthread_rwlockattr_t通常设为NULLint pthread_rwlock_init( pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr)//销毁一个读写锁int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//获取读锁int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);//获取写锁int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);//读写解锁int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);适用的场景为条件满足立刻被唤醒,否则挂起等待。条件需要被mutex保护。 条件变量类型为pthread_cond_t,必须被初始化为PTHREAD_COND_INITIALIZER,等价于调用pthread_cond_init(…, NULL)
#include <pthread.h>//初始化一个条件变量,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是 PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。int pthread_cond_init( pthread_cond_t *restrict cond, const pthread_condxattr_t *restrict attr)//销毁一个条件变量int pthread_cond_destroy(pthread_cond_t *cond);//等待条件发生int pthread_cond_wait( pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);int pthread_cond_timedwait( pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);/*pthread_cond_timedwait类似,只是当等待超时的时候返回一个错误值ETIMEDOUT。超时的时间用timespec结构指定。struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */};注意timespec的时间是绝对时间而非相对时间,因此需要先调用gettimeofday函数获得当前时间,再转换成timespec结构,加上偏移量。*///条件满足后唤醒等待的线程int pthread_cond_signal(pthread_cond_t *cond);int pthread_cond_broadcast(pthread_cond_t *cond);/*两者的区别是前者会唤醒单个线程,而后者会唤醒多个线程。*/gcc -g -pthread cond.c -lpthread -o test 运行test后,输出如下:
counter: 0counter(main): 0counter(decrement): 0 decrement_counter lock! counter(increment): 0 increment_counter lock!counter++(before): 0counter++(after): 1increment_counter will unlock!counter--(before): 1counter--(after): 0decrement_counter will unlock! counter(main): 1是不是觉得上面的输出有点不可思议?明明decrement_counter lock为什么increment_counter lock还能成功。关键在于 pthread_cond_wait(&counter_nonzero, &counter_lock)在等待是,实际上已经将锁unlock,并将线程挂起在等待队列,等待唤醒。pthread_cond_signal(&counter_nonzero); 实际上会对对应mutex一个lock操作。
在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。
它和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在 A线程里输出的很可能是B线程的出错信息。
要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。
/*实现功能,创建5个线程,每个线程将日志记录到thread%d.log日志文件中*/#include <malloc.h>#include <pthread.h>#include <stdio.h>static pthread_key_t thread_log_key;void write_to_thread_log (const char* message){ //从键读取线程数据 FILE* thread_log = (FILE*) pthread_getspecific (thread_log_key); fprintf (thread_log, "%s/n", message); }void close_thread_log (void* thread_log){ fclose ((FILE*) thread_log);}void* thread_function (void* args){ char thread_log_filename[20]; FILE* thread_log; sprintf (thread_log_filename, "thread%d.log", (int) pthread_self ()); thread_log = fopen (thread_log_filename, "w+"); //为当前线程的键指定线程数据 pthread_setspecific (thread_log_key, thread_log); //记录日志 write_to_thread_log ("Thread starting."); return NULL; }int main (){ int i; pthread_t threads[5]; //创建一个键,close_thread_log为线程退出时调用的析构函数 pthread_key_create (&thread_log_key, close_thread_log); for (i = 0; i < 5; ++i) pthread_create (&(threads[i]), NULL, thread_function, NULL); for (i = 0; i < 5; ++i) pthread_join (threads[i], NULL); //销毁键 pthread_key_delete(thread_log_key); return 0;}新闻热点
疑难解答