友情提示:man funtion_name 可以查看 funtion_name的介绍。
4.2 线程的取消
一个线程A可以通过 pthread_cancel 让另外一个线程B退出。在线程B会返回 PTHREAD_CANCELED。通常,一个线程可以分为以下三类:
1> 可以被异步取消。在线程运行的任何时候都可以被取消。
2> 可以被同步取消。线程不是在任何时候都可以被取消。这样,对该线程的取消请求只在线程运行到指定点上才执行取消操作。
3> 不可以被取消。对该类线程的取消操作会被忽略。
通常的线程为第2种情况。
4.2.1 同步和异步线程
使用pthread_setcanceltype可以设置一个线程的异步取消属性。比如,pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
你可以设定同步取消类线程的取消点,用pthread_testcancel,但是应该确保在调用位置线程被取消后不会造成资源泄漏或者其他错误。
你可以使用pthread_setcanceltype 保护你的重要的不可被取消的操作,现执行
pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, NULL);//让线程不可被取消
//do sth
pthread_setcancelstate (PTHREAD_CANCEL_ENABLE , NULL);//让线程可以被取消
如下是个转帐的例子,
Listing 4.6 (critical-section.c) Protect a Bank Transaction with a Critical Section
#include <pthread.h>
#include <stdio.h>
#include <string.h>
/* An array of balances in accounts, indexed by account number. */
float* account_balances;
/* Transfer DOLLARS from account FROM_ACCT to account TO_ACCT. Return
0 if the transaction succeeded, or 1 if the balance FROM_ACCT is
too small. */
int process_transaction (int from_acct, int to_acct, float dollars)
{
int old_cancel_state;
/* Check the balance in FROM_ACCT. */
if (account_balances[from_acct] < dollars)
return 1;
/* Begin critical section. */
pthreadAD_CANCEL_DISABLE, &old_cancel_state);
/* Move the money. */
account_balances[to_acct] += dollars;
account_balances[from_acct] -= dollars;
/* End critical section. */
pthread_setcancelstate (old_cancel_state, NULL);
return 0;
}
_setcancelstate (PTHRE
4.3 线程私有数据(Thread-Specific Data)
我都知道同一个程序中所有的线程在同一个地址空间,他们共享几乎所有资源。但是每个线程有自己的调用栈。这使得他们可以执行各自的代码。
可是有时候线程有需要有独立使用的变量。GNU/Linux支持为线程提供私有数据域的功能。
你可以创建任意多个你想创建的线程私有数据,每个的类型都是void* 。每个项依据一个 key 来存取。使用pthread_key_create 创建一个
新的key。
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
他的第二个参数是一个清理函数。他如果非空的话,将会在线程退出时自动被调用。即使在线程被取消时也会被调用。
使用pthread_setspecific可以设定线程私有数据的值。要读取线程私有数据使用pthread_getspecific函数。
如下是个例子,
Listing 4.7 (tsd.c) Per-Thread Log Files Implemented with Thread-Specific Data
#include <malloc.h>
#include <pthread.h>
#include <stdio.h>
#include <fcntl.h>
/* The key used to associate a log file pointer with each thread. */
static pthread_key_t thread_log_key;
/* Write MESSAGE to the log file for the current thread. */
void write_to_thread_log (const char* message)
{
FILE* thread_log = (FILE*) pthread_getspecific (thread_log_key);
fprintf (thread_log, “%s/n”, message);
}
/* Close the log file pointer THREAD_LOG. */
void close_thread_log (void* thread_log)
{
fclose ((FILE*) thread_log);
}
void* thread_function (void* args)
{
char thread_log_filename[20];
FILE* thread_log;
/* Generate the filename for this thread’s log file. */
sprintf (thread_log_filename, “thread%d.log”, (int) pthread_self ());
/* Open the log file. */
thread_log = fopen (thread_log_filename, “w”);
/* Store the file pointer in thread-specific data under thread_log_key. */
pthread_setspecific (thread_log_key, thread_log);
write_to_thread_log (“Thread starting.”);
/* Do work here... */
fprintf(stderr,"/tthread_log_key's address is %x /n",&thread_log_key);
return NULL;
}
int main ()
{
int i;
pthread_t threads[5];
/* Create a key to associate thread log file pointers in
thread-specific data. Use close_thread_log to clean up the file
pointers. */
pthread_key_create (&thread_log_key, close_thread_log);
/* Create threads to do the work. */
for (i = 0; i < 5; ++i)
pthread_create (&(threads[i]), NULL, thread_function, NULL);
/* Wait for all threads to finish. */
for (i = 0; i < 5; ++i)
pthread_join (threads[i], NULL);
return 0;
}
4.3.1 清理函数(Cleanup Handlers)
线程清理函数将在线程退出时候调用,它有一个void* 类型的参数,这个参数在它注册时指定。一般情况下线程没有退出或者被取消时,资源被显式释放时,线程清理函数因该被移除。
注册线程清理函数使用 pthread_cleanup_push,它要和pthread_cleanup_pop的调用相匹配。pthread_cleanup_pop用来移除处于线程清理函数栈栈顶的清理函数。如果
pthread_cleanup_pop的参数值不为零,那么栈顶函数将被执行。
线程的清理函数会在以下情况自动调用:
1> 当一个线程被取消时,它的所有的在线程清理函数栈中的清理函数将会按照与被安装时相反的顺序执行。
2> 当线程调用pthead_exit(3)时,同上。
3> 当线程调用pthread_cleanup_pop时给了非零参数,那么线程清理函数栈栈顶的清理函数将被执行。
下面例子演示了怎么使用,
Listing 4.8 (cleanup.c) Program Fragment Demonstrating a Thread
Cleanup Handler
#include <malloc.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
/* Allocate a temporay buffer. */
void* allocate_buffer (size_t size)
{
return malloc (size);
}
/* Deallocate a temporary buffer. */
void deallocate_buffer(void* buffer)
{
fprintf(stderr, "thread%d called deallocate_buffer./n", (int)pthread_self());
free (buffer);
}
void* do_some_work (void* flag)
{
/* Allocate a temporary buffer. */
void* temp_buffer = allocate_buffer (1024);
/* Register a cleanup handler for this buffer, to deallocate it in case the /
* thread exits or is cancelled. */
pthread_cleanup_push (deallocate_buffer, temp_buffer);
/*Do some work might call pthread_exit or might be cancelled*/
if ((int)flag){
fprintf(stderr, "thread%d will call pthread_exit./n",/
(int)pthread_self());
pthread_exit(0);
}
/* Unregister the cleanup handler. Because we pass a nonzero value,this/
* actually performs the cleanup by calling deallocate_buffer. */
pthread_cleanup_pop (1);
return 0;
}
int main(int argc, char** argv)
{
pthread_t pthrd1,pthrd2;
//int flag = 1;
pthread_create(&pthrd1, NULL, do_some_work,(void*)1);//This thread will call pthread_exit
//flag = 0;
pthread_create(&pthrd2, NULL, do_some_work,(void*)0);//exit normally
pthread_join(pthrd1, NULL);
pthread_join(pthrd2, NULL);
return 0;
}
4.3.2 C++中线程的清理
C++倾向于在对象的析构函数中清理释放资源。但是,如果一个线程调用了pthread_exit ,C++不保证所有线程栈上的自动变量的析构函数能被调用。
这可以通过在线程函数的最上层的函数中调用pthread_exit实现(在要结束的位置抛出一个异常,在顶层函数中捕获该异常,并调用pthread_exit)。
如下是个例子,
#include <iostream>
#include <pthread.h>
using namespace std;
class CThreadExitException{
public:
/* Create an exception-signaling thread exit with RETURN_VALUE. */
CThreadExitException (void* return_value)
:thread_return_value (return_value)
{}
/* Actually exit the thread, using the return value provided in the constructor.*/
void* DoThreadExit()
{
pthread_exit (thread_return_value);
}
private:
/* The return value that will be used when exiting the thread. */
void* thread_return_value;
};
void do_some_work (int exit_flag )
{
while (1) {
/* Do some usefull things*/
if (exit_flag == 1){
cout<<"/tCThreadExitException throw /n"<<endl;
throw CThreadExitException(/* thread's return value*/ NULL);
}
}
}
//extern void* thread_function (void* arg);
void* thread_function (void*arg)
{
arg = arg;
try{
do_some_work (1);
}
catch (CThreadExitException ex) {
/* Some function indicated we should exit the thread.*/
cout<<"/t CThreadExitException !/n"<<endl;
ex.DoThreadExit ();
}
return NULL;
}
int main(void)
{
pthread_t pid;
pthread_create(&pid, NULL, thread_function, NULL);
pthread_join(pid,NULL);
}
4.4 同步和临界段
调试一个有多个线程的程序是困难的,因为有些错误情况不容易重现。你可能一次执行没有问题,但下次执行就失败了。不可能让你的系统以相同的方式调度所有的线程像以前一次一样。
多线程程序失败的终极原因是,多个线程有冲突的访问相同的资源。
4.4.1 条件竞争(Race conditions)
Listing 4.10 ( job-queue1.c) Thread Function to Process Jobs from the Queue
#include <malloc.h>
struct job {
/* Link field for linked list. */
struct job* next;
/* Other fields describing work to be done... */
};
/* A linked list of pending jobs. */
struct job* job_queue;
/* Process queued jobs until the queue is empty. */
void* thread_function (void* arg)
{
while (job_queue != NULL) {
/* Get the next available job. */
struct job* next_job = job_queue;
/* Remove this job from the list. */
job_queue = job_queue->next;//*************************************[#]<翻译成机器代码时为多条指令>
/* Carry out the work. */
process_job (next_job);
/* Clean up. */
free (next_job);
}
return NULL;
}
假如两个线程正好同时结束了任务,此时任务队列中只有一个任务。线程A发现队列非空,执行到上面代码的#处而为完成执行修改时,被线程B打断,等线程B执行完#处所有指令时
线程A得到CPU继续执行,而并不知到对列已经被B修改过了。从而线程A,B执行了相同的任务。再往坏处考虑就是 线程A修改队列指针时由于引用的NULL指针而发生错误。
为了消除这种竞争,你需要一种方法来保证操作的元子性。即,一旦开始执行将会被全部执行完毕。上面例子中,对队列非空的判断,修改队列指针,作为一个元子操作的化,将不会出现
上面的错误。
4.4.2 互斥标记 (Mutexes)
Mutexes就如同在厕所门上加上一个门插,第一个人进去后把门插上,操作,完成后,打开门插,这样下一个人就可以进去...
创建一个phread_mutex_t 类型的变量用pthead_mutex_init,如下
pthread_mutex_t mutex;
pthread_mutex_init (&mutex, NULL);
另外一个方法也可以完成如上的操作,
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
一个线程可以通过 pthread_mutex_lock 锁上一个互斥标记(mutex)。这时,如果已经有线程对此标志加了锁,那么后到的线程就会阻塞,直到先加的锁被解除。
相对应,使用pthread_mutex_unlock 解锁。而使用 pthread_mutex_trylock时如果已经有线程加了锁,则会立即返回EBUSY,而不会等待。如果你先调用了 pthread_mutex_lock,
再调用 pthread_mutex_trylock,将会返回 0。
例子,
Listing 4.11 ( job-queue2.c) Job Queue Thread Function, Protected by a Mutex
#include <malloc.h>
#include <pthread.h>
struct job {
/* Link field for linked list. */
struct job* next;
/* Other fields describing work to be done... */
};
/* A linked list of pending jobs. */
struct job* job_queue;
/* A mutex protecting job_queue. */
pthread_mutex_t job_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
/* Process queued jobs until the queue is empty. */
void* thread_function (void* arg)
{
while (1) {
struct job* next_job;
/* Lock the mutex on the job queue. */
pthread_mutex_lock (&job_queue_mutex);
/* Now it’s safe to check if the queue is empty. */
if (job_queue == NULL)
next_job = NULL;
else {
/* Get the next available job. */
next_job = job_queue;
/* Remove this job from the list. */
job_queue = job_queue->next;
}
/* Unlock the mutex on the job queue because we’re done with the
queue for now. */
pthread_mutex_unlock (&job_queue_mutex);
/* Was the queue empty? If so, end the thread. */
if (next_job == NULL)
break;
/* Carry out the work. */
process_job (next_job);
/* Clean up. */
free (next_job);
}
return NULL;
}