kwseeker

学编程:找代码,读代码,改代码,写代码;少看书。但少看不是不看,看一本书要限制在一天内。任务是通读书中主要的概念,明白大致是干什么用的,形成一个大概的框架;然后一周内干掉书中全部代码,代码不理解再浏览书中相关章节,注意是浏览。或者刷博客。代码实在刷不懂,可以先刷后面,待日后重新刷;因为书中代码有些部分可能出自后面的章节。代码总是在多次刷过之后,拨开乌云见日月的。。。

导航

Linux系统编程@多线程编程(二)

线程的操作


线程标识

线程的ID表示数据类型:pthread_t (内核中的实现是unsigned long/unsigned int/指向pthread结构的指针(不可移植)几种类型)

1.对两个线程ID进行比较

#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread tid2);
//返回值:若相等则返回非0值,不相等返回0

2.获取自身的线程id

#include <pthread.h>
pthread_t pthread_self(void);

 

线程创建

int pthread_create(pthread_t *thread, pthread_attr_t *attr,
                                  void *(*start_routine)(void *), void *arg);
//thread: 参数是一个指针,当线程成功创建时,返回创建线程ID。
//attr: 用于指定线程的属性
//start_routine: 该参数是一个函数指针,指向线程创建后要调用的函数。
//arg: 传递给线程函数的参数。

编程模型

...
pthread_t ntid;
... void *thr_fn(void *arg) {...} ... int main() { ... int err; ... err=pthread_create(&ntid, NULL, thr_fn, NULL);  //此处的参数根据具体需要会改变 if(err!=0)                        //注意不要忘记检查线程创建是否成功 ...; ... }

线程终止

单个线程的三种退出方式:

1)通过return从线程函数返回;

2)通过调用pthread_exit()函数使线程退出;

3)被同一进程其他线程取消

需要注意的地方:一是,主线程中如果从main函数返回或是调用了exit函数退出主线程,则整个进程终止,此时所有的其他线程也将终止。二是,如果主线程调用pthread_exit函数,则仅仅是主线程消亡,进程不会结束,其他线程也不会结束,直到所有的线程都结束时,进程才结束。三: 当一个线程调用pthread_exit函数退出,或简单从启动例程中返回时,进程中的其他线程可以通过调用pthread_join函数获得该线程的退出状态。

pthread_create和pthread_exit函数的无类型指针参数能传递的数值不止一个,该指针可以传递包含更复杂信息的结构的地址,但是注意这个结构所使用的内存在调用者完成调用以后必须仍然是有效的,否则会出现无效或非法内存访问。例如,在调用线程的栈上分配了该结构,那么其他的线程使用这个结构时内存内容可能已经改变了。又如线程在自己的栈上分配了一个结构然后把指向这个结构的指针传给pthread_exit,那么当调用pthread_join的线程试图使用这个结构时,这个栈可能已经被撤销,这块内存也以另作他用。

 1 /*
 2 当调用pthread_exit后,当前线程操作的结构可能会发生改变,但是仍然能够被其他线程所访问。
 3  */
 4 
 5 #include <pthread.h>
 6 #include <stdio.h>
 7 #include <stdlib.h>
 8 
 9 struct foo
10 {
11     int a, b, c, d;
12 };
13 
14 void printfoo(const char* s, const struct foo *fp)
15 {
16     printf(s);
17     printf(" structure at 0x%x\n", (unsigned)fp);
18     printf(" foo.a=%d\n", fp->a);
19     printf(" foo.b=%d\n", fp->b);
20     printf(" foo.c=%d\n", fp->c);
21     printf(" foo.d=%d\n", fp->d);
22 }
23 
24 void *thr_fn1(void *arg)
25 {
26     struct foo foo={1,2,3,4};
27     printfoo("thread 1:\n", &foo);
28     pthread_exit((void*)&foo);
29 }
30 
31 void *thr_fn2(void *arg)
32 {
33     printf("thread 2: ID is %d\n", pthread_self());
34     pthread_exit((void*)0);
35 }
36 
37 int main(void)
38 {
39     int err;
40     pthread_t tid1, tid2;
41     struct foo *fp;
42     
43     err=pthread_create(&tid1, NULL, thr_fn1, NULL);
44     if (err!=0)
45         printf("can't create thread 1: %s\n", strerror(err));
46     err=pthread_join(tid1, (void*)&fp);
47     if (err!=0)
48         printf("can't join with thread 1: %s\n", strerror(err));
49 
50     sleep(1);
51     printf("parent starting second thread\n");
52 
53     err=pthread_create(&tid2, NULL, thr_fn2, NULL);
54     if (err!=0)
55         printf("can't create thread 2: %s\n", strerror(err));
56 
57     sleep(1);
58     printfoo("parent: \n", fp);
59     exit(0);
60 }
View Code

 

1.线程取消相关函数

1)int pthread_cancel(pthread_t thread)
  发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。还有信号处理的过程,处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。

#include <pthread.h>
int pthread_cancel(pthread_t tid);
//成功返回0,失败放回错误编号(并不会设置errno)

2)int pthread_setcancelstate(int state,   int *oldstate)   

设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,
分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复。  

3)int pthread_setcanceltype(int type, int *oldtype)   
设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED(默认)和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入原来的取消动作类型值。  

4)void pthread_testcancel(void)
是说pthread_testcancel在不包含取消点,但是又需要取消点的地方创建一个取消点,以便在一个没有包含取消点的执行代码线程中响应取消请求.
线程取消功能处于启用状态且取消状态设置为延迟状态时,pthread_testcancel()函数有效
如果在取消功能处处于禁用状态下调用pthread_testcancel(),则该函数不起作用。
务必仅在线程取消线程操作安全的序列中插入pthread_testcancel()。除通过pthread_testcancel()调用以编程方式建立的取消点意外,pthread标准还指定了几个取消点。测试退出点,就是测试cancel信号.

 执行取消操作存在一定的危险。大多数危险都与完全恢复不变量释放共享资源有关。取消线程时一定要格外小心,否则可能会使互斥保留为锁定状态,从而导致死锁状态。或者,已取消的线程可能保留已分配的内存区域,但是系统无法识别这一部分内存,从而无法释放它。实例?如何解决?http://www.cnblogs.com/mydomain/archive/2011/08/15/2139830.html

取消点:
线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。

线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。

pthreads标准指定了几个取消点,其中包括:

man 7 pthreads 查看取消点函数列表(下面的四种情况都可以查到)
(1)通过pthread_testcancel调用以编程方式建立线程取消点。
(2)线程等待pthread_cond_wait或pthread_cond_timewait()中的特定条件。
(3)被sigwait(2)阻塞的函数
(4)一些标准的库调用。通常,这些调用包括线程可基于阻塞的函数。

缺省情况下,将启用取消功能。有时,您可能希望应用程序禁用取消功能。如果禁用取消功能,则会导致延迟所有的取消请求,直到再次启用取消请求。
根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及
read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。
但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标.
即如下代码段:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();

注意:
程序设计方面的考虑,如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用.

 

取消一个线程的过程:

1) 其他线程通过调用pthread_cancel()函数,向目标线程发送取消请求(cancellation request)。

2) 取消请求发出后,根据目标线程的cancel state来决定取消请求是否会到达目标线程

a. 如果目标线程的cancel state是PTHREAD_CANCEL_ENABLE(默认),取消请求会到达目标线程。

b. 如果目标线程的cancel state是PTHREAD_CANCEL_DISABLE,取消请求会被放入队列。直到目标线程的cancel state变为PTHREAD_CANCEL_ENABLE,取消请求才会从队列里取出,发到目标线程。

3) 取消请求到达目标线程后,根据目标线程的cancel type来决定线程何时取消:

a. 如果目标线程的cancel type是PTHREAD_CANCEL_DEFERRED(默认),目标线程并不会马上取消,而是在执行下一条cancellation point的时候才会取消。有很多系统函数都是cancellation point,详细的列表可以在Linux上用man 7 pthreads查看。除了列出来的cancellation point,pthread_testcancel()也是一个cancellation point。就是说目标线程执行到pthread_testcancel()函数的时候,如果该线程收到过取消请求,而且它的cancel type是PTHREAD_CANCEL_DEFERRED,那么这个线程就会在这个函数里取消(退出),这个函数就不再返回了,目标线程也没有了。

b. 如果目标线程的cancel type是PTHREAD_CANCEL_ASYNCHRONOUS,目标线程会立即取消(这里的“立即”只是说目标线程不用等执行到属于cancellation point的函数的时候才会取消,它会在获得调度之后立即取消,因为内核调度会有延时,所以并不能保证时间上的“立即”)。

 1 void thread_function(void *arg)
 2 {
 3 /**
 4 * 线程准备执行一些关键工作,在这个过程中不希望被取消。
 5 * 所以先通过pthread_setcancelstate()将本线程的cancel state
 6 * 设为disabled。
 7 */
 8 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
 9 /* 执行关键工作 */
10 ...
11 /**
12 * 关键工作执行完成,可以被取消。
13 * 通过pthread_setcancelstate()将本线程的cancel state
14 * 设为enabled。
15 */
16 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
17 /**
18 * 调用pthread_testcancel()函数,检查一下在cancel state
19 * 为disabled状态的时候,是否有取消请求发送给本线程。
20 * 如果有的话就取消(退出)。
21 */
22 pthread_testcancel();
23 /**
24 * pthread_testcancel()返回了,表明之前没有取消请求发送给本线程,
25 * 继续其余的工作。
26 * 这时候如果有取消请求发送给本线程,会在下一次执行到
27 * cancellation point的时候(例如sleep(), read(), write(), ...)时取消。
28 */
29 ...
30 /**
31 * 从这里开始,函数里不再包含cancellation point了。
32 * 如果收到取消请求,将无法取消。所以先把本线程的cancel type
33 * 设为asynchronous,收到取消请求将立即取消。
34 */
35 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
36 /* 不包含cancellation point的代码 */
37 ...
38 }
View Code
 1 /*
 2 * pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancel_state);
 3 * 其中将cancel状态改为PTHREAD_CANCEL_DISABLE,将原来的状态保存至old_cancel_state变量;
 4 * pthread_cancel(tid);    //发送终止信号
 5 * pthread_setcancelstate(old_cancel_state, NULL); 恢复cancel状态
 6  */
 7 #include <pthread.h>
 8 #include <stdio.h>
 9 #include <stdlib.h>
10 #include <unistd.h>
11 
12 /*这个线程中本要睡10秒,5秒时发cancel信号后立即退出*/
13 void* thread1(void* data)
14 {
15     int i=0;
16     int oldtype;
17 
18     //pthread_setcanceltype(PTHREAD_CANCEL_DEFFERED, &oldtype); 这一句是默认的
19     printf("thread1 start......\n");
20     while(i<10)
21     {
22         printf("thread1 running...\n");
23         sleep(1);       //sleep()是取消点,所以读5秒时会从这里退出
24         i++;
25     }
26 
27     printf("thread1 exit...\n");   //while内收到cancel信号的话,此句不会执行
28     pthread_exit(NULL);
29 }
30 void* thread2(void* data)
31 {
32     int i=0;
33     int old_cancel_state;
34 
35     pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancel_state);
36     printf("thread2 start......\n");
37     while(i<10)
38     {
39         printf("thread2 running...\n");
40         sleep(1);
41         i++;
42     }
43 
44     printf("thread2 exit...\n");
45     pthread_setcancelstate(old_cancel_state, NULL); 
46     pthread_exit(NULL);
47 }
48 
49 int main()
50 {
51     pthread_t tid1, tid2;
52 
53     if (pthread_create(&tid1, NULL, thread1, NULL) != 0) 
54     {
55         exit(1);
56     }
57 
58     sleep(5);    // sleep a while.
59     pthread_cancel(tid1);    //发送终止信号
60 
61 
62 
63     if (pthread_create(&tid2, NULL, thread2, NULL) != 0) 
64     {
65         exit(1);
66     }
67     sleep(5);
68     pthread_cancel(tid2);
69 
70     pthread_join(tid1, NULL); 
71     pthread_join(tid2, NULL);
72     printf("main thread exit...\n");
73 
74     return 0;
75 }
View Code

思考:

1.线程取消的作用,在什么时候会使用线程取消?

  1)共享资源的原子操作,一段时间内只允许一个线程对共享资源的访问,其余线程全部pthread_cancel();同时还要使用pthread_setcancelstate()对此线程进行保护。

2. 某某函数是 Cancellation Points,这种方法是容易令人混淆的,其实真正的 Cancellation Points 只是在这些函数中 Cancellation Type 被修改为 PHREAD_CANCEL_ASYNCHRONOUS 和修改回 PTHREAD_CANCEL_DEFERRED 中间的一段时间。怎么理解?

3. 系统编程中的同步与异步的概念?

 

线程终止的清理工作

线程终止后保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。

最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。

在POSIX线程API中提供了一个pthread_cleanup_push()/ pthread_cleanup_pop()函数,
对用于自动释放资源—从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。

API定义如下:
void pthread_cleanup_push(void (*routine) (void *), void *arg) //清理函数名,传入清理函数的参数
void pthread_cleanup_pop(int execute)

pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,void routine(void *arg)函数
在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push() 的调用将在清理函数栈中形成一个函数链;
从pthread_cleanup_push的调用点到pthread_cleanup_pop之间的程序段中的终止动作(包括调用pthread_exit()、响应cancel请求信号、非零参数调用pthread_cleanup_pop()异常终止不包括return都将执行pthread_cleanup_push()所指定的清理函数。

 

 1 #include <pthread.h>
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 
 5 void cleanup(void *arg)
 6 {
 7     printf("cleanup: %s\n",(char*)arg);
 8 }
 9 
10 void *thr_fn1(void *arg)
11 {
12     printf("thread 1 start\n");
13     pthread_cleanup_push(cleanup, "thread 1 first handler");
14     pthread_cleanup_push(cleanup, "thread 1 second handler");
15     printf("thread 1 push complete\n");
16     if (arg)
17         return((void*)1);
18     pthread_cleanup_pop(0);
19     pthread_cleanup_pop(0);
20     return((void*)1);
21 }
22 
23 void *thr_fn2(void *arg)
24 {
25     printf("thread 2 start\n");
26     pthread_cleanup_push(cleanup, "thread 2 first handler");
27     pthread_cleanup_push(cleanup, "thread 2 second handler");
28     printf("thread 2 push complete\n");
29     if (arg)
30         pthread_exit((void*)2);
31     pthread_cleanup_pop(0);
32     pthread_cleanup_pop(0);
33     return((void*)2);
34 }
35 
36 int main(void)
37 {
38     int err;
39     pthread_t tid1, tid2;
40     void *tret;
41 
42     err=pthread_create(&tid1, NULL, thr_fn1, (void*)1);
43     if (err!=0)
44         printf("can not create thread1: %s\n", strerror(err));
45     err=pthread_create(&tid2, NULL, thr_fn2, (void*)1);
46     if (err!=0)
47         printf("can not create thread2: %s\n", strerror(err));
48 
49     err=pthread_join(tid1, &tret);
50     if (err!=0)
51         printf("can not join with thread1: %s\n", strerror(err));
52     printf("thread 1 exit code %d\n", (int)tret);
53     err=pthread_join(tid2, &tret);
54     if (err!=0)
55         printf("can not join with thread2: %s\n", strerror(err));
56     printf("thread 2 exit code %d\n", (int)tret);
57 
58     exit(0);
59 }
View Code

 

在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到 pthread_cleanup_pop()时

是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。

pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:

#define pthread_cleanup_push(routine,arg) \ 
{ 
struct _pthread_cleanup_buffer _buffer; \ 
_pthread_cleanup_push (&_buffer, (routine), (arg));

#define pthread_cleanup_pop(execute) \ 
_pthread_cleanup_pop (&_buffer, (execute)); \
}

可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。

在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。

 1 pthread_cleanup_push(pthread_mutex_unlock, (void*) &mut);
 2 pthread_mutex_lock(&mut);
 3 /* do some work */
 4 pthread_mutex_unlock(&mut);
 5 pthread_cleanup_pop(0);
 6 或者
 7 void cleanup(void *arg)
 8 {    
 9     pthread_mutex_unlock(&mutex);
10 }
11 
12 void* thread0(void* arg)
13 {    
14     pthread_cleanup_push(cleanup, NULL); // thread cleanup handler    p
15     thread_mutex_lock(&mutex);    
16     pthread_cond_wait(&cond, &mutex);    
17     pthread_mutex_unlock(&mutex);    
18     pthread_cleanup_pop(0);    
19     pthread_exit(NULL);
20 }
View Code

 

线程分离

调用pthread_detach(),当线程终止时,和它相关的系统资源将会被自动释放,系统不用也不能用pthread_join()等待其退出。有的时候分离线程更好些,因为它潜在地减少了一个线程回收的同步点。

API:

#include <pthread.h>
int pthread_detach(pthread_t tid);

  

线程同步

线程同步的三种方式:互斥量(Mutex)、信号灯(Semophore)、条件变量(Conditions)

互斥量(mutex):本质上是一把锁。

对互斥量加锁后,任何其他对互斥量视图加锁的线程都会被阻塞,直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥量加锁,其他线程则会回去再次等待它重新变为可用。

互斥量的使用:

数据类型:pthread_mutex_t

使用前必须对互斥量进行初始化,对静态分配的互斥量可以把它置为常量PTHREAD_MUTEX_INITIALIZER;也可以通过调用pthread_mutex_init函数进行初始化(对动态分配的互斥量也有效)。对于动态的分配互斥量(例如malloc),在释放互斥量时要调用pthread_mutex_destory。

互斥量的缺点

互斥量需要时间来加锁和解锁
  互斥量尽量少用,够用即可;每个互斥量保护的区域应尽量大 
互斥量的本质是串行执行
  如果很多线程需要频繁地加锁同一互斥量,则线程大部分时间都在等待=>损害性能
  互斥量保护的数据/代码包含彼此无关的片段=>一个互斥量分解为几个小互斥量=>任意时刻需要小互斥量的线程减少=>减少线程的等待时间
  互斥量应该足够多=>有意义的地步
  每个互斥量保护的区域应该尽量少
 

锁的粒度: 优点     缺点
粗粒度   编码简单   并发性不好->吞吐量小
       额外开销较小
细粒度   吞吐量大   额外开销多
       编码较为复杂

加锁的位置影响锁的粒度,根据具体需要设置。

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//第一个参数是之前声明的互斥量,第二个参数是该互斥量的属性。 int pthread_mutex_destory(pthread_mutex_t *mutex); //成功返回0,失败返回错误编号

//互斥量的属性:
PTHREAD_MUTEX_FAST_NP(默认类型):快速型;
PTHREAD_MUTEX_RECURSIVE_NP:递归型;???
PTHREAD_MUTEX_ERRORCHECK_NP:错误检测型;互斥量已经上了锁,后续上锁会失败而不会阻塞。后续的上锁操作会返回错误EDEADLK。
//互斥量的属性设置函数
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);
//第一个参数是之前声明的属性变量,第二个参数是上述3个属性之一。

总结下互斥量使用属性的代码处理过程

pthread_mutex_t mutex;        //1 声明互斥量
pthread_mutexattr_t attr;     //2 声明互斥量的属性
pthread_mutexattr_init(&attr);  //3 对互斥量属性做初始化
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE_NP); //4 设置互斥量属性类型为递归型
pthread_mutex_init(&mutex,&attr);//5 初始化带有属性参数的互斥量
pthread_mutex_destroy(&attr);   //6 释放属性变量

加锁与解锁

#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_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则会失败,不能锁住互斥量,而返回EBUSY。而不像pthread_mutex_lock()会等待,直到互斥量被解锁。

避免死锁

死锁产生原因:同一线程对一个互斥量加锁两次;两个线程有竞争一个线程拥有另一进程想要锁住的互斥量,而想锁住另一个线程拥有的互斥量,而对方和这个线程一样(可以控制多个线程对多个互斥量加锁顺序一致来避免);

读写锁

读写锁的三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态

线程    对共享资源控制       状态

pthread1      占有          写加锁   读加锁   未加锁

pthread2      请求          无论要读还是写都阻塞

                 以读模式请求加锁,获取访问权

                 以写模式请求加锁,阻塞等待

即写加锁时,不允许任何访问;读加锁时,允许多个进程同时对共享资源进行读操作,但是不允许同时进行写操作。适用于对数据结构读的次数远大于写的情况。

/*读写锁使用前需要初始化,释放其底层的内存前必须销毁*/
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
                        const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//Both return: 0 if OK, error number on failure
// attr: 传入空指针时,初始化执行默认属性
  
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
//All return: 0 if OK, error number on failure
 
//不阻塞形式的加锁方式 int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); //Both return: 0 if OK, error number on failure

 

信号量

信号灯其实就是一个计数器,也是一个整数。每一次调用wait操作将会使semaphore值减一,而如果semaphore值已经为0,则wait操作将会阻塞。每一次调用post操作将会使semaphore值加一

 

Item * p =queue_list;
Queue_list=queue_list->next;
Process_job(p);
Free(p);

 

将这些操作用到上面的问题中。工作线程每一次调用wait操作,如果此时链表中没有节点,则工作线程将会阻塞,直到链表中有节点。生产者线程在每次往链表中添加节点后调用post操作,信号灯值会加一。这样阻塞的工作线程就会停止阻塞,继续往下执行

 

sem_t semaphoreVar;
sem_init(sem_t semaphore Var, 0);    //第二个必须为0
sem_wait();
//或者
sem_trywait();    //非阻塞的操作

 

结合了互斥量和信号灯的实例:

#include <malloc.h>
#include <pthread.h>
#include <semaphore.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;
/* A semaphore counting the number of jobs in the queue. */
sem_t job_queue_count;
/* Perform one-time initialization of the job queue. */
void initialize_job_queue ()
{
    /* The queue is initially empty. */
    job_queue = NULL;
    /* Initialize the semaphore which counts jobs in the queue. Its
    initial value should be zero. */
    sem_init (&job_queue_count, 0, 0);
}
/* Process queued jobs until the queue is empty. */
void* thread_function (void* arg)
{
    while (1) {
        struct job* next_job;
        /* Wait on the job queue semaphore. If its value is positive,
        indicating that the queue is not empty, decrement the count by1. If the queue is empty, block until a new job is enqueued. */
        sem_wait (&job_queue_count);
        /* Lock the mutex on the job queue. */
        pthread_mutex_lock (&job_queue_mutex);
        /* Because of the semaphore, we know the queue is not empty. 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);
        /* Carry out the work. */
        process_job (next_job);
        /* Clean up. */
        free (next_job);
    }
    return NULL;
}
/* Add a new job to the front of the job queue. */
void enqueue_job (/* Pass job-specific data here... */)
{
    struct job* new_job;
    /* Allocate a new job object. */
    new_job = (struct job*) malloc (sizeof (struct job));
    /* Set the other fields of the job struct here... */
    /* Lock the mutex on the job queue before accessing it. */
    pthread_mutex_lock (&job_queue_mutex);
    /* Place the new job at the head of the queue. */
    new_job->next = job_queue;
    job_queue = new_job;
    /* Post to the semaphore to indicate that another job is available. If threads are blocked, waiting on the semaphore, one will become unblocked so it can process the job. */
    sem_post (&job_queue_count);
    /* Unlock the job queue mutex. */
    pthread_mutex_unlock (&job_queue_mutex);
}                

 

 

 

条件变量

利用线程间共享的全局变量进行同步。

条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。

  互斥锁用于短期等待
  条件变量用于长期等待

条件变量是由互斥量保护的。线程在改变条件变量之前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因此必须锁定互斥量以后才能计算条件。

Before a condition variable is used, it must first be initialized. A condition variable, represented by the pthread_cond_t data type, can be initialized in two ways. We can assign the constant PTHREAD_COND_INITIALIZER to a statically-allocated condition variable, but if the condition variable is allocated dynamically, we can use the pthread_cond_init function to initialize it.

We can use the pthread_mutex_destroy function to deinitialize a condition variable before freeing its underlying memory.

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,
                      pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond); //Both return: 0 if OK, error number on failure // attr: 设置为NULL,使用默认属性 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);
//在前者基础上添加了时间限制 //Both return: 0 if OK, error number on failure

//关于时间计数,参看相应gettimeofday的博文
struct timespec {
  time_t tv_sec;
  long_t tv_nsec;
}

传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。

唤醒一个等待进程的两个函数

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
//Both return: 0 if OK, error number on failure

 

线程控制


线程属性

POSIX.1定义的线程属性

线程的属性结构

//定义在/usr/include/bits/pthreadtypes.h
typedef struct
{
	int detachstate;   //线程的分离状态
	int schedpolicy;  //线程调度策略
	struct sched_param schedparam;  //线程的调度参数
	int inheritsched;  //线程的继承性
	int scope;      //线程的作用域
	size_t guardsize;   //线程栈末尾的警戒缓冲区大小
	int stackaddr_set;
	void* stackaddr;   //线程栈的位置
	size_t stacksize;   //线程栈的大小
}pthread_attr_t;

typedef union
{
    char __size[__SIZEOF_PTHERAD_ATTR_T];  //位操作:每一位决定一种属性
    long int __align;
} pthread_attr_t;

 

修改属性的过程

1.声明一个pthread_attr_t 的变量;
2.调用pthread_attr_init函数,使其包含期望的属性值;
3.创建新线程时,将该属性变量指针传递给新创建的线程;
4.显式调用pthread_attr_destroy()释放pthread_attr_t变量
  ①此时,该变量不是被内存回收
  ②可继续用于其他的线程的pthread_attr_init()
线程的属性操作注意事项:
修改线程的属性:属性决定新线程的状态
   属性值不能直接设置
   使用相关函数进行操作
   初始化用pthread_attr_init(),在pthread_create之前使用
   去除初始化用pthread_attr_destroy()
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t   *attr); 
//Both return: 0 if OK, error number on failure
 

将线程属性detachstate设置为PTHERAD_CREATE_DETACHED,以分离状态启动;设置为PTHREAD_CREATE_JOINABLE,正常启动线程,应用程序可以获得线程的终止状态。

#include <pthread.h>
//获取当前detachstate int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr, int *detachstate); int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); //Both return: 0 if OK, error number on failure

实例:

 1 #include "apue.h"
 2 #include <pthread.h>
 3 
 4 int makethread(void *(*fn)(void *), void *arg)
 5 {
 6     int             err;
 7     pthread_t       tid;
 8     pthread_attr_t  attr;
 9 
10     err = pthread_attr_init(&attr);
11     if (err != 0)
12         return(err);
13     err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
14     if (err == 0)
15         err = pthread_create(&tid, &attr, fn, arg);
16     pthread_attr_destroy(&attr);
17     return(err);
18 }
View Code

 

作用域属性 scope

int pthread_attr_setscope(pthread_attr_t *attr, int scope)
int pthread_attr_getscope(pthread_attr_t *attr, int *scope)
//PTHREAD_SCOPE_SYSTEM: 线程在系统级上竞争资源(绑定)
//PTHREAD_SCOPE_PROCESS:线程在进程内部竞争资源(非绑定的)

关于线程的绑定

轻进程(LWP: Light Weight Process)
1) 内核线程,内核的调度实体
2) 系统对线程资源的分配和对线程的控制是通过轻进程实现
3) 一个轻进程可以控制一个或多个线程
非绑定状态(默认状态),操作系统控制:
1) 启动多少个轻进程
2) 哪些轻进程控制哪些线程
绑定状态
1) 将某线程固定绑定在一个轻进程
2) 提高响应速度
    通过设置被绑定的轻进程的优先级和调度级来进一步提高

 

线程调度策略

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
Int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy);
//SCHED_FIFO:先进先出
//SCHED_RR:轮转法
//SCHED_OTHER:其他
//成功时,这些函数返回0;如果错误,它们返回非0值。EINVAL:policy 值无效。
pthread_attr_setschedpolicy() 函数调用attr 引用的线程属性对象的调度策略属性为 policy指定的值。
pthread_attr_getschedpolicy() 通过 policy 指向的缓存区返回线程属性对象 attr的调度策略属性。

SCHED_FIFO策略允许一个线程运行直到有更高优先级的线程准备好,或者直到它自愿阻塞自己。在SCHED_FIFO调度策略下,当有一个线程准备好时,除非有平等或更高优先级的线程已经在运行,否则它会很快开始执行。

SCHED_RR(轮循)策略是基本相同的,不同之处在于:如果有一个SCHED_RR策略的线程执行了超过一个固定的时期(时间片间隔)没有阻塞,而另外的SCHED_RR或SCHBD_FIPO策略的相同优先级的线程准备好时运行的线程将被抢占以便准备好的线程可以执行。

当有SCHED_FIFO或SCHED_RR策赂的线程在一个条件变量上等持或等持加锁同一个互斥量时,它们将以优先级顺序被唤醒。即,如果一个低优先级的SCHED_FIFO线程和一个高优先织的SCHED_FIFO线程都在等待锁相同的互斥量,则当互斥量被解锁时,高优先级线程将总是被首先解除阻塞。

 
 
线程调度参数

int pthread_attr_setschedparam(pthread_attr_t *attr,const struct sched_param *param);

int pthread_attr_getschedparam(pthread_attr_t *attr,struct sched_param *param);

sched_param结构定义

struct sched_param

{

    int sched_priority;

};

实际上是设置优先级的相关参数

获得系统支持的线程优先权的最大和最小值

int sched_get_priority_max(int policy)

int sched_get_priority_min(int policy)

 

线程的继承性:新建的线程是否继承创建者线程中定义的调度策略

int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched);
int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);
//PTHREAD_INHERIT_SCHED: 新建线程将继承创建者线程中定义的调度策略;忽略pthread_create()调用中定义的所有调度属性
//PTHREAD_EXPLICIT_SCHED: 将使用pthread_create()调用中的属性

 

线程栈大小设置

当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,可能需要增大线程栈的默认大小。

int pthread_attr_getstacksize ( const pthread_attr_t *attr, size_t *size );
int pthread_attr_setstacksize ( pthread_attr_t *attr, size_t size );

int pthread_attr_getstack ( const pthread_attr_t *attr, void **stackaddr, size_t *size );
int pthread_attr_setstack ( pthread_attr_t *attr, void *stackaddr, size_t size );
//函数pthread_attr_getstack和pthread_attr_setstack函数可以同时操作栈地址和栈大小两个属性

 

栈保护区大小(stack guard size)

在线程栈顶留出一段空间,防止栈溢出。当栈指针进入这段保护区时,系统会发出错误,通常是发送信号给线程。该属性默认值是PAGESIZE大小,该属性被设置时,系统会自动将该属性大小补齐为页大小的整数倍。当改变栈地址属性时,栈保护区大小通常清零。

int pthread_attr_getguardsize ( const pthread_attr_t *attr, size_t *guardsize );
int pthread_attr_setguardsize ( pthread_attr_t *attr, size_t guardsize );

 

 

 

同步属性

 

重入

线程安全函数:当且仅当被多个并发进程反复调用时,它会一直产生正确的结果。

可重入函数:线程安全函数的一种,特点是他们被多个线程调用时,不会引用任何共享数据。可重入函数童话仓比不可重入的线程安全函数效率要高一些,因为不需要同步操作。

四类不安全的线程:

1.不保护共享变量的函数

2.保持跨越多个调用的状态函数

3.返回指向静态变量指针的函数

4.调用线程不安全函数的函数

 A nonreentrant version of getenv

#include <limits.h>
#include <string.h>

static char envbuf[ARG_MAX];
extern char **environ;

char *getenv(const char *name)
{
    int i, len;

    len = strlen(name);
    for (i = 0; environ[i] != NULL; i++) {
        if ((strncmp(name, environ[i], len) == 0) &&
          (environ[i][len] == '=')) {
            strcpy(envbuf, &environ[i][len+1]);
            return(envbuf);
        }
    }
    return(NULL);
}
A reentrant (thread-safe) version of getenv
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>

extern char **environ;

pthread_mutex_t env_mutex;
static pthread_once_t init_done = PTHREAD_ONCE_INIT;

static void
thread_init(void)
{
    pthread_mutexattr_t attr;

    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&env_mutex, &attr);
    pthread_mutexattr_destroy(&attr);
}

int
getenv_r(const char *name, char *buf, int buflen)
{
    int i, len, olen;

    pthread_once(&init_done, thread_init);
    len = strlen(name);
    pthread_mutex_lock(&env_mutex);
    for (i = 0; environ[i] != NULL; i++) {
        if ((strncmp(name, environ[i], len) == 0) &&
          (environ[i][len] == '=')) {
            olen = strlen(&environ[i][len+1]);
            if (olen >= buflen) {
                pthread_mutex_unlock(&env_mutex);
                return(ENOSPC);
            }
            strcpy(buf, &environ[i][len+1]);
            pthread_mutex_unlock(&env_mutex);
            return(0);
        }
    }
    pthread_mutex_unlock(&env_mutex);
    return(ENOENT);
}

 

线程私有数据(TSD)

一次性初始化

可以通过互斥量实现,也可以使用pthread_once

 

pthread_once_t once_control=PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *once_control, void(*init_routine)(void));
//once_control    控制变量
//init_routine      初始化函数(只会运行一次)
//成功返回0,失败返回错误编号

 

实例:

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <stdlib.h>
 4 
 5 pthread_once_t once=PTHREAD_ONCE_INIT;
 6 
 7 void run(void)
 8 {
 9     printf("Function run is running in thread %u\n", pthread_self());
10 }
11 void *thread1(void *arg)
12 {
13     pthread_t threadId=pthread_self();
14     printf("Current threadID is %u\n", threadId);
15     pthread_once(&once, run);    //一次性初始化
16     printf("thread1 ends\n");
17 }
18 void *thread2(void *arg)
19 {
20     pthread_t threadId=pthread_self();
21     printf("Current threadID is %u\n", threadId);
22     pthread_once(&once, run);
23     printf("thread2 ends\n");
24 }
25 
26 int main()
27 {
28     pthread_t threadId1, threadId2;
29     pthread_create(&threadId1, NULL, thread1, NULL);
30     pthread_create(&threadId2, NULL, thread2, NULL);
31     sleep(3);
32     printf("main thread exit\n");
33     exit(0);
34 }
View Code

TSD

线程的私有数据是线程自己的各个函数都可以访问,对其他线程屏蔽的。

在分配线程私有数据之前,要创建与该数据关联的键。用于获取对线程私有数据的访问权。

 

int pthread_key_create(pthread_key_t *key, void (*destructor) (void *));
//destructor: 线程退出时将以key所关联的数据调用该函数,释放所分配的绑在这个键上的内存块
//key:一键多值,各线程可根据自己的需要填入不同的值

 static struct pthread_key_struct_pthread_keys[PTHREAD_KEYS_MAX] = {0,NULL}};
//一个键应只被创建一次,因此这个函数常和函数pthread_once 一起使用

 

//从一个键读取线程私有数据
void *pthread_getspecific( pthread_key_t key );
//为一个键设置线程私有数据
int pthread_setspecific( pthread_key_t key , const void *value );
//删除一个键
int pthread_key_delete( pthread_key_t key );

实例:

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <pthread.h>
 4 
 5 pthread_key_t key;
 6 
 7 void *thread2(void *arg)
 8 {
 9     int tsd=5;
10     printf("thread %u is running\n", pthread_self());
11     pthread_setspecific(key, (void*)tsd);
12     printf("thread %u returns %d\n", pthread_self(), pthread_getspecific(key));
13 }
14 void *thread1(void *arg)
15 {
16     int tsd=0;
17     pthread_t threadid2;
18     printf("thread %u is running\n", pthread_self());
19     pthread_setspecific(key, (void*)tsd);        //为一个键设置线程私有数据
20     pthread_create(&threadid2, NULL, thread2, NULL);
21     sleep(1);
22     printf("thread %u returns %d\n", pthread_self(), pthread_getspecific(key));    //从一个键值获取私有数据
23 }
24 
25 int main(int argc, char const *argv[])
26 {
27     pthread_t threadid1;
28     printf("main thread begins running\n");
29     pthread_key_create(&key, NULL);                //创建一个键值
30     pthread_create(&threadid1, NULL, thread1, NULL);
31     sleep(3);
32     pthread_key_delete(key);
33     printf("main thread exit\n");
34 
35     return 0;
36 }
View Code

 

 

 

 

 

取消选项

 

线程和信号

 

线程和fork

 

线程与I/O

 

 

 

 

 

 

 

部分参考:http://www.cnblogs.com/lijunamneg/archive/2013/01/25/2877211.html

 

posted on 2015-06-10 20:40  kwseeker  阅读(342)  评论(0编辑  收藏  举报