线程概念:
-
典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。 -
进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。 -
首先Linux并不存在真正的线程,Linux的线程是使用进程模拟的。当我们需要在一个进程中同时运行多个执行流时,我们并不可以开辟多个进程执行我们的操作(32位机器里每个进程认为它独享4G的内存资源),此时便引入了线程,例如当我们既需要下载内容,又需要浏览网页时,此时多线程便起了作用。线程是承担调度的基本单位,一个进程可拥有多个线程,它的执行力度比进程更加细致,线程资源共享。 -
“进程——资源分配的最小单位,线程——程序执行的最小单位” -
一个进程至少包含一个线程,进程是运行的程序,程序是静态的概念,进程是动态的概念。 -
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,可以用线程,也可以用进程间的通信。
使用线程的理由:
- 总的来说就是:进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)。
- 使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
- 使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
- 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
- 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
- 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
POSIX线程库:与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的要使用这些函数库,要通过引入头文件#include<pthread.h>链接这些线程函数库时要使用编译器命令的“-lpthread”选项,多线程开发在 Linux 平台上已经有成熟的 pthread 库支持。
其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。条件操作有 5 种操作:创建,销毁,触发,广播和等待。
线程开发API:
线程的创建:pthread_create()函数
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
功能:
创建一个新的线程
参数:
thread:指向的内存单元被设置为新创建线程的线程ID
attr:设置线程的属性,attr为NULL表?示使?用默认属性
start_routine:新创建的线程从start_rtn函数的地址开始运行
该函数只有一个万能 指针参数arg,如果需要向start_rtn函数传递的参数不止一个
那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
注意:void*表示指针类型不限制,只要是指针就行
因为pthread并非Linux系统的默认库,而是POSIX线程库。
在Linux中将其作为一个库来使用,因此加上 -lpthread(或-pthread)以显式链接该库。
函数在执行错误时的错误信息将作为返回值返回,并不修改系统全局变量errno,当然也无法使用perror()打印错误信息。
线程退出:pthread_exit()函数
#include <pthread.h>
void pthread_exit(void *retval);
功能:
使用函数pthread_exit退出线程,这是线程的主动行为;由于一个进程中的多个线程是共享数据段的
因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放,但是可以用pthread_join()函数来同步并释放资源。
参数:
retval: pthread_exit()调用线程的返回值,可由其他函数如pthread_join来检索获取。
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
1)线程只是从启动例程中返回,返回值是线程的退出码。
2)线程可以被同一进程中的其他线程取消
一个线程可以调用pthread_cancel终止同一进程中的另一个线程。
3)线程调用pthread_exit:
注意:
调用exit()的话,主进程会终止
从线程函数return,这种方法法对主线程不适用从main函数return相当于调用exit。
线程等待:pthread_join()函数
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能:
调pthread_join()函数,以阻塞的方式等待thread指定的线程结束。
当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
参数:
thread: 线程标识符,即线程ID,标识唯一线程。
retval: 用户定义的指针,用来存储被等待线程的返回值。
退出函数返回的是一个空指针类型,接受函数也必须用一个指针来接收。
但是函数给出的参数是接收指针的地址,即,接收到的指针值写入给出的地址处的指针变量。
返回值:若成功返回0,否则返回错误编号
为什么要用这个函数?
1、代码中如果没有pthread_join,主线程会很快结束从而使整个进程结束,
从而使创建的线程没有机会开始执行就结束了。
2、在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算
主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果
也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到pthread_join()方法了。
即pthread_join()的作用可以这样理解:主线程等待子线程的终止。
也就是在子线程调用了pthread_join()方法后面的代码只有等到子线程结束了才能执行。
信号发送函数:pthread_cancel()
#include <pthread.h>
int pthread_cancel(pthread_t thread)
功能:
发送终止信号给thread线程
参数:
thread:要发送的目标线程id
返回值:如果成功则返回0,否则为非0值。
函数说明:
pyhread_cancel函数只是给线程发送了一个请求该请求是希望可以将该线程终止。
所以对于该请求的话,只是对于线程的一个建议
线程也可能就不会立即终止,会继续运行,直到运行到取消点的时候该线程才会退出
取消点的理解:
- 取消点是线程检查是否被取消并按照请求进行动作的一个位置.
- 取消点是如何出现的呢?
- 对于取消点对于使用某些函数就会出现取消点
- 例如:sleep,wait,waitpid,waitid,send等函数
设置取消的状态函数:pthread_setcancelstate()
#include <pthread.h>
pthread_setcancelstate(int state, int* oldstate);
功能:
这个函数可以设置取消的状态,有两种状态PTHREAD_CANCEL_ENABLE(可取消状态)
PTHREAD_CANCEL_DISABLE(不可取消状态)。
参数:
state:将当前状态改为state
oldstate:将该线程原先的状态放到oldtype所指向的空间里面
返回值:
成功返回0,失败返回错误码
这两步是一个原子操作。
对于pthread_cancel函数默认的是PTHREAD_CANCEL_ENABLE可取消状态。
当状态设为PTHREAD_CANCEL_DISABLE时,对于pthread_cancel的调用并不会杀死线程。
相反,该取消请求对于这个线程还处于挂起状态(也就是未决),直到线程的取消状态变为PTHREAD_CANCEL_ENABLE时
线程将在下一个取消点上对所有的挂起请求进行处理。
设置取消类型函数:pthread_setcanceltype()
#include <thread.h>
int pthread_testcanceltype(int type, int* oldtype);
参数:
type:将取消的类型设置为type
oldtype:将该线程的取消类型放到oldtype所指向的空间里面
返回值:
成功返回0,失败返回错误码
1、我们默认的取消类型是推迟取消。也就是会运行到取消点再取消
对于可以设置的取消类型有PTHREAD_CANCEL_DEFERRED(推迟取消),也就是默认的取消类型。
2、PTHRAED_CANCEL_ASYNCHRONOUS(异步取消),采用异步取消之后,线程可以在任意时间取消,不是非得到取消点才可以取消。
加取消点函数:pthread_testcancel()
- 对于一个线程没有调用上面说的可以产生取消点函数,那么该线程就没有取消点,也就无法被取消,取消请求也就会一直挂起,不会被线程处理。
- 所以如果要对线程进行取消请求的话,可以自己给线程加上取消点
- pthread_testcancel函数就可以在程序自己加上取消点。
#include <thread.h>
void pthread_testcancel(void);
调用pthread_testcancel函数,如果有某个取消请求正处于挂起状态的话
而且取消并没有被设置为无效,那么该线程就会立即被取消。
但是,如果取消被设置为无效的话,那么pthread_testcancel函数调用就没与效果了。
线程脱离:pthread_detach()函数 一个线程或者是可汇合(joinable,默认值),或者是脱离的(detached)。当一个可汇合的线程终止时,它的线程ID和退出状态将留存到另一个线程对它调用pthread_join。脱离的线程却像守护进程,当它们终止时,所有相关的资源都被释放,我们不能等待它们终止。
#include <pthread.h>
int pthread_detach(pthread_t thread);
作用:
从状态上实现线程分离,注意不是指该线程独自占用地址空间。
本函数通常由想让自己脱离的线程使用
参数:thread:线程标识符
返回值:若成功返回0,否则返回错误编号
线程分离状态: 指定该状态,线程主动与主控线程断开关系。线程结束后(不会产生僵尸线程),其退出状态不由其他线程获取,而直接自己自动释放(自己清理掉PCB的残留资源)。网络、多线程服务器常用。
- 进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资源仍存于系统中,导致内核认为该进程仍存在。(注意进程没有这一机制)
- 也可使用 pthread_create函数参2(线程属性)来设置线程分离。
- 一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止(或者进程终止被回收了)。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误(22号错误)。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
线程ID获取:pthread_self()函数
#include <pthread.h>
pthread_t pthread_self(void);
返回:调用线程的ID,可以和pthread_detach函数配合使用
线程ID比较:pthread_equal()函数
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
返回:若相等则返回非0值,否则返回0
pthread_create()函数、pthread_exit()函数和pthread_join()函数搭配实例:
#include<stdio.h>
#include <pthread.h>
#include<stdlib.h>
struct num
{
int a,b;
};
void *add(void* arg)
{
struct num *p;
p=(struct num*)malloc(128);
p->a=12;
p->b=18;
printf("a=%d\n",((struct num*)arg)->a);
printf("b=%d\n",((struct num*)arg)->b);
printf("a+b=%d\n",((struct num*)arg)->a+((struct num*)arg)->b);
printf("son id=%ld\n",(unsigned long)pthread_self());
pthread_exit((void*)&p);
}
int main()
{
pthread_t td;
int retnu;
struct num son;
struct num* sonret;
son.a=10;
son.b=-3;
retnu=pthread_create(&td,NULL,add,(void*)&son);
if(retnu!=0){
printf("pthread create fail\n");
}
pthread_join(td,(void**)&sonret);
printf("进程结束\n");
printf("son return:a=%d,b=%d\n",((struct num*)sonret)->a,((struct num*)sonret)->b);
printf("main id=%ld,td=%ld\n",(unsigned long)pthread_self(),(unsigned long)td);
return 0;
}
实例验证子线程和主线程共享资源:
#include<stdio.h>
#include <pthread.h>
int i=1;
void * func1(void*arg)
{
i=i+1;
printf("thread1 i=%d\n",i);
pthread_exit((void*)&i);
}
void * func2(void*arg)
{
i=i+1;
printf("thread2 i=%d\n",i);
pthread_exit((void*)&i);
}
int main()
{
pthread_t t1;
pthread_t t2;
int t1return;
int t2return;
int* th1;
int* th2;
t1return=pthread_create(&t1,NULL,func1,(void*)&i);
if(t1return!=0){
printf("pthread create fail\n");
}
pthread_join(t1,(void**)&th1);
printf("thread1 return:i=%d\n",*(int *)th1);
t2return=pthread_create(&t2,NULL,func2,(void*)&i);
if(t2return!=0){
printf("pthread create fail\n");
}
pthread_join(t2,(void**)&th2);
printf("thread2 return:i=%d\n",*(int *)th2);
printf("main thread i=%d\n",i);
return 0;
}
对于多线程程序来说,我们往往需要对这些多线程进行同步。同步(synchronization)是指在一定的时间内只允许某一个线程访问某个资源。而在此时间内,不允许其它的线程访问该资源。我们可以通过互斥锁(mutex),条件变量(condition variable)和读写锁(reader-writer lock)来同步资源。
互斥锁:
- 互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前运行。
- 在设计时需要规定所有的线程必须遵守相同的数据访问规则。只有这样,互斥机制才能正常工作。操作系统并不会做数据访问的串行化。如果允许其中的某个线程在没有得到锁的情况下也可以访问共享资源,那么即使其它的线程在使用共享资源前都获取了锁,也还是会出现数据不一致的问题。
- 互斥变量用pthread_mutex_t数据类型表示。在使用互斥变量前必须对它进行初始化,可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态地分配互斥量(例如通过调用malloc函数),那么在释放内存前需要调用pthread_mutex_destroy。
- 在使用互斥锁前,需要定义互斥锁(全局变量),定义互斥锁对象形式为:pthread_mutex_t lock;
- 还可以用宏 PTHREAD_MUTEX_INITIALIZER 来初始化静态分配的互斥锁,如下:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- 使用互斥锁能保证在一个线程内的代码跑完后再去跑别的线程的代码。
创建互斥锁:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
功能:
该函数用于C函数的多线程编程中,互斥锁的初始化。
pthread_mutex_init()函数是以动态方式创建互斥锁的。
参数:
mutex 是指向要初始化的互斥锁的指针。
参数attr指定了新建互斥锁的属性。如果参数attr为NULL
则使用默认的互斥锁属性,默认属性为快速互斥锁 。
互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性
不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。
返回值:
函数成功完成之后会返回零,其他任何返回值都表示出现了错误。
函数成功执行后,互斥锁被初始化为锁住态。
互斥锁属性:
互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:
- PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
- PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
- PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
- PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
互斥锁销毁:pthread_mutex_destroy()
#include <pthread.h>
int pthread_mutex_destroy( pthread_mutex_t * mutex);
参数:
指向要初始化的互斥锁的指针
返回值:
成功后都返回 0,否则返回错误编号以指名错误。
加锁函数:pthread_mutex_lock()
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_trylock(pthread_mutex_t* mutex);
功能:
描述 pthread_mutex_lock()函数锁住由mutex指定的mutex 对象。
pthread_mutex_trylock()调用在参数mutex指定的mutex对象当前被锁住的时候立即返回
除此之外,pthread_mutex_trylock()跟pthread_mutex_lock()功能完全一样。
参数:指向要操作的的mutex对象
返回值:
pthread_mutex_lock() 成功:返回0,否则返回一个错误的提示码
pthread_mutex_trylock() 在成功获得了一个mutex的锁后返回0,否则返回一个错误提示码错误
解锁函数:pthread_mutex_unlock()
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t* mutex);
参数:
指向要操作的的mutex对象
返回值:
成功则返回0, 出错则返回错误编号.
函数的综合应用:
#include<stdio.h>
#include <pthread.h>
#include <unistd.h>
int i=1;
pthread_mutex_t mutex;
void * func1(void*arg)
{
pthread_mutex_lock(&mutex);
while(1){
i=i+1;
printf("thread1 i=%d\n",i);
sleep(2);
if(i==7){
pthread_mutex_unlock(&mutex);
pthread_exit((void*)&i);
}
}
}
void * func2(void*arg)
{
pthread_mutex_lock(&mutex);
while(1){
i=i+1;
printf("thread2 i=%d\n",i);
sleep(2);
if(i==13){
pthread_mutex_unlock(&mutex);
pthread_exit((void*)&i);
}
}
}
}
int main()
{
pthread_t t1;
pthread_t t2;
int t1return;
int t2return;
int* th1;
int* th2;
pthread_mutex_init(&mutex,NULL);
t1return=pthread_create(&t1,NULL,func1,(void*)&i);
if(t1return!=0){
printf("pthread create fail\n");
}
sleep(2);
pthread_mutex_lock(&mutex);
for(;;){
i++;
printf("main thread i=%d\n",i);
sleep(2);
if(i==10){
pthread_mutex_unlock(&mutex);
break;
}
}
pthread_join(t1,(void**)&th1);
printf("thread1 return:i=%d\n",*(int *)th1);
t2return=pthread_create(&t2,NULL,func2,(void*)&i);
if(t2return!=0){
printf("pthread create fail\n");
}
pthread_join(t2,(void**)&th2);
printf("thread2 return:i=%d\n",*(int *)th2);
pthread_mutex_destroy(&mutex);
printf("main over\n");
return 0;
}
以下是程序每次执行的顺序:
thread1 i=2
thread1 i=3
thread1 i=4
thread1 i=5
thread1 i=6
thread1 i=7
main thread i=8
main thread i=9
main thread i=10
thread1 return:i=10
thread2 i=11
thread2 i=12
thread2 i=13
thread2 return:i=13
main over
通过chmod改变文件权限 可以写一个脚本来验证上述代码,只需要将写好的文件加上可执行权限即可。
什么条件下可能造成死锁:
- 在线程一里面想先获取锁一,然后获取锁二,在线程二里面想先获取锁二再获取锁一,如果在线程一获取锁一并且没有解锁时,同时未获得锁二,如果此时线程二获得了锁二那么将造成死锁。
条件变量:
- 条件变量是线程另一可用的同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
- 条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。
- 条件变量使用之前必须首先初始化,pthread_cond_t数据类型代表的条件变量可以用两种方式进行初始化,可以把常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量,但是如果条件变量是动态分配的,可以使用pthread_cond_destroy函数对条件变量进行去除初始化(deinitialize)。
- 注意: 不能用多个线程初始化同一个条件变量,当一个线程要使用条件变量的时候确保它是未被使用的。
创建及销毁条件变量:
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t*cond);
注销一个条件变量需要调用pthread_cond_destroy()
只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。
参数:
cond:指向定义的cond变量
参数attr为空指针时,函数创建的是一个缺省的条件变量。
否则条件变量的属性将由attr中的属性值来决定
返回值:
若成功执行,函数pthread_cond_init()将返回零,并把新建条件变量的id放在cond变量中
否则,将返回一个代表错误的错误码。
等待:
- 等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait()
- 其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待
- 无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。
- 激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t * cond, pthread_mutex_t * mutex, cond struct timespec * timeout);
返回:若成功返回0,否则返回错误编号
触发:
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
返回:若成功返回0,否则返回错误编号
这两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程
而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。
互斥锁和条件变量综合使用:
#include<stdio.h>
#include <pthread.h>
#include <unistd.h>
int i=0;
pthread_cond_t cond;
pthread_mutex_t mutex;
void * func1(void*arg)
{
printf("线程一运行\n");
while(1){
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
if(i==7){
printf("新的循环\n");
i=0;
pthread_mutex_unlock(&mutex);
}
}
pthread_exit((void*)&i);
}
void * func2(void*arg)
{
printf("线程二运行\n");
while(1){
pthread_mutex_lock(&mutex);
i=i+1;
printf("t2:i=%d\n",i);
if(i==7){
pthread_cond_signal(&cond);
}
pthread_mutex_unlock(&mutex);
sleep(1);
}
pthread_exit((void*)&i);
}
int main()
{
pthread_t t1;
pthread_t t2;
int t1return;
int t2return;
int* th1;
int* th2;
pthread_cond_init(&cond,NULL);
pthread_mutex_init(&mutex,NULL);
t1return=pthread_create(&t1,NULL,func1,(void*)&i);
if(t1return!=0){
printf("pthread create fail\n");
}
t2return=pthread_create(&t2,NULL,func2,(void*)&i);
if(t2return!=0){
printf("pthread create fail\n");
}
pthread_join(t1,(void**)&th1);
pthread_join(t2,(void**)&th2);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
printf("main over\n");
return 0;
}
将程序输出到文件:
fhn@ubuntu:~/thread$ ./test >> result.txt &
[2] 18838
其中>>表示将运行的结果追加到test.ret.txt中 &表示在后台运行
[2] 18838(这是test 进程的ID号)
大佬博客:条件变量和互斥锁使用实例
学习过程补充:
- C中struct只是类型声明,没有内存空间的分配,而static变量是需要分配内存的。这种解释可以说明为什么在结构体内包含static变量会出错。
- void (*add)(void);这个只是用来声明add是函数指针的,不能用来函数定义,这个add可以指向其他函数的指针,但是要求函数类型和add的函数类型相同。
- 程序中各种变量、常量的存储位置
- 关键字restrict只用于限定指针,表明本指针是访问一个数据对象的惟一且初始的方式。
- 生产者消费者问题
- 这是一个非常经典的多线程题目,题目大意如下:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,所有生产者和消费者都是异步方式运行的,但它们必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经装满产品且尚未被取走的缓冲区中投放产品。
- 消费者将g_count每次减去1,生产者将g_count每次加1;消费者会判断g_count的大小,如果g_count==0那么消费者线程要阻塞;但是它还会一直占有锁,所以这样就阻止了其它线程对g_count的操作;此时我们要用到条件变量;调用pthread_cond_wait(&g_cond, &g_mutex);让互斥锁g_mutex在这个g_cond条件上等待;
|