常用多线程函数
多线程编程设计的主要部分为以下几个部分 1.线程创建 ,2.参数传递,3.数据同步,4.结果返回(线程通信)以及 5.线程销毁。
pthread_create
pthread_create 作用:线程创建
/*
线程将会执行一个线程函数,线程格式声明
*/
void * Thread_Function(void *);
/*
thread:所创建的线程号。
attr:所创建的线程属性,这个将在后面详细说明。
start_routine:即将运行的线程函数。
art:传递给线程函数的参数。
*/
int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void*),
void *restrict arg);
gcc –o createthread –lpthread createthread.c
如果想传递参数给线程函数,可以通过其参数arg,其类型是void *。当需要传递多个参数,可以将这些参数封装成一个结构体来传递。(参数类型是void *,此参数不可以被提前释放掉)。
/* Prints x’s to stderr. The parameter is unused. Does not return. */
void* print_xs (void* unused){
while (1)
fputc (‘x’, stderr);
return NULL;
}
int main ()
{
pthread_t thread_id;
/* Create a new thread. The new thread will run the print_xs function. */
pthread_create (&thread_id, NULL, &print_xs, NULL);
/* Print o’s continuously to stderr. */
while (1)
fputc (‘o’, stderr);
return 0;
}
pthread_join
所有线程都有一个线程号,也就是Thread ID。其类型为pthread_t。通过调用pthread_self()函数可以获得自身的线程号。
线程的本质。 在Linux中,新建的线程并不是在原先的进程中,而是系统通过一个系统调用clone()。该系统copy了一个和原先进程完全一样的进程,并在这个进程中执行线程函数。不过这个copy过程和fork不一样。copy后的进程和原先的进程共享了所有的变量,运行环境(系统资源)。这样,原先进程中的变量变动在copy后的进程中便能体现出来。
pthread_join 作用: 使一个线程等待另一个线程结束
意义:(工程代码中如果没有pthread_join,主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。使用pthread_join,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。)(通过pthread_join函数让主线程阻塞,直到所有线程都已经执行后退出)
/*
thread:等待退出线程的线程号。
value_ptr:退出线程的返回值。
*/
int pthread_join(pthread_t thread, void **value_ptr);
int main ()
{
pthread_t thread1_id;
pthread_t thread2_id;
struct char_print_parms thread1_args;
struct char_print_parms thread2_args;
/* Create a new thread to print 30,000 x’s. */
thread1_args.character = ’x’;
thread1_args.count = 30000;
pthread_create (&thread1_id, NULL, &char_print, &thread1_args);
/* Create a new thread to print 20,000 o’s. */
thread2_args.character = ’o’;
thread2_args.count = 20000;
pthread_create (&thread2_id, NULL, &char_print, &thread2_args);
/* Make sure the first thread has finished. */
pthread_join (thread1_id, NULL);
/* Make sure the second thread has finished. */
pthread_join (thread2_id, NULL);
/* Now we can safely return. */
return 0;
}
线程属性
通过pthread_join()函数来使主线程阻塞等待其他线程退出,这样主线程可以清理其他线程的环境。但是还有一些线程,更希望自己来清理退出的状态,它们不希望主线程调用pthread_join来等待他们。
这一类线程的属性称为detached。如果我们在调用pthread_create()函数的时候将属性设置为NULL,则表明我们希望所创建的线程采用默认的属性,即jionable。如果需要将属性设置为detached,则
void * start_run(void * arg){
while(1){
//do some work
}
}
int main(int argc, const int* argv[])
{
pthread_t thread_id;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&thread_id, &attr, start_run,NULL);
pthread_attr_destroy(&attr);
sleep(5);
exit(0);
}
(在线程设置为joinable后,可以调用pthread_detach()使之成为detached。反之则不行。如果线程已经调用pthread_join()后,则再调用pthread_detach()则不会有任何效果。)
// 函数调用成功返回0
int pthread_cancel(pthread_t thread);
通过设置自身的属性来决定如何结束
/*
type:要设置的状态,可以为PTHREAD_CANCEL_DEFERRED或者为PTHREAD_CANCEL_ASYNCHRONOUS。
结束点设置: 最常用的创建终结点就是调用pthread_testcancel()的地方。
该函数除了检查同步终结时的状态,其他什么也不做。其用来设置终结状态的。
*/
int pthread_setcanceltype(int type, int *oldtype);
通过调用pthread_setcancelstate()来设置终结类型,即该线程可不可以被终结
/*
state:终结状态,可以为PTHREAD_CANCEL_DISABLE或者PTHREAD_CANCEL_ENABLE
*/
int pthread_setcancelstate(int state, int *oldstate);
pthread_cond_wait
pthread_cond_wait作用:用于阻塞当前线程,等待别的线程使用 pthread_cond_signal() 或者 pthread_cond_broadcast()将其唤醒。
(pthread_cond_wait() 需要与 pthread_cond_mutex() 搭配使用)
(pthread_cond_wait() 进入阻塞状态后就会自动释放 mutex,当其它线程将它唤醒时,使pthread_cond_wait() 通过(返回)时,该线程又自动获得该 mutex)
条件变量 和 互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; /*初始化互斥锁*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; /*初始化条件变量*/
(线程的并发过程中起关键作用)
( pthread_cond_t 一般会搭配 pthread_mutex_t 使用。当线程间通信时操作共享内存时,需要用到锁。当锁住的共享变量发生改变时,可能需要通知相应的线程(因为可能该共享变量涉及到多个线程),这时就需要用到 pthread_cond_t 这种条件变量来精准的通知某个或几个线程, 让他们执行相应的操作
pthread_cond_t 涉及两个函数,一个是 pthread_cond_signal 函数,它在一个线程中,用来发送信号。一个是 pthread_cond_wait 函数,它在另一个线程中,用来接收信号。当线程执行到pthread_cond_wait 函数时,它会释放相应的锁,让其它线程获得锁继续执行,这样其它线程才有机会给它发信号;当它接收到信号时,会重新去获得锁,如果没有获得锁,就阻塞等待,直到获得锁,才执行接收信号的相应操作。)
//主程序结束后需要释放 互斥锁和 条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//链表消息的结点
struct msg
{
int num;
struct msg *next;
};
struct msg *head = NULL; //头指针
struct msg *temp = NULL; //节点指针
//静态方式初始化互斥锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_producer = PTHREAD_COND_INITIALIZER;
void *producer(void *arg)
{
while (1) //线程正常不会解锁,除非收到终止信号
{
pthread_mutex_lock(&mutex); //加锁
temp = malloc(sizeof(struct msg));
temp->num = rand() % 100 + 1;
temp->next = head;
head = temp; //头插法
printf("---producered---%d\n", temp->num);
pthread_mutex_unlock(&mutex); //解锁
pthread_cond_signal(&has_producer); //唤醒消费者线程
usleep(rand() % 3000); //为了使该线程放弃cpu,让结果看起来更加明显。
}
return NULL;
}
void *consumer(void *arg)
{
while (1) //线程正常不会解锁,除非收到终止信号
{
pthread_mutex_lock(&mutex); //加锁
while (head == NULL) //如果共享区域没有数据,则解锁并等待条件变量
{
pthread_cond_wait(&has_producer, &mutex); //我们通常在一个循环内使用该函数
}
temp = head;
head = temp->next;
printf("------------------consumer--%d\n", temp->num);
free(temp); //删除节点,头删法
temp = NULL; //防止野指针
pthread_mutex_unlock(&mutex); //解锁
usleep(rand() % 3000); //为了使该线程放弃cpu,让结果看起来更加明显。
}
return NULL;
}
int main(void)
{
pthread_t ptid, ctid;
srand(time(NULL)); //根据时间摇一个随机数种子
//创建生产者和消费者线程
pthread_create(&ptid, NULL, producer, NULL);
pthread_create(&ctid, NULL, consumer, NULL);
//主线程回收两个子线程
pthread_join(ptid, NULL);
pthread_join(ctid, NULL);
return 0;
}
pthread_cond_signal
pthread_cond_signal 作用:发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行。(如果没有线程处在阻塞等待状态, pthread_cond_signal()也会成功返回)
注意: 最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,则是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal()调用最多发送唤醒信息一次。(在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续阻塞等待,而且规范要求,pthread_cond_signal() 至少唤醒一个pthread_cond_wait()上的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程。)
线程同步
(互斥锁 加锁解锁(互斥量):
互斥量也称为互斥锁,对共享资源进行锁定,保证同一时刻只能有一个线程去操作。
注意: 互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁)
(在加锁与解锁中间的代码,叫做共享资源。)
#include <pthread.h>
// 初始化互斥锁 返回:若成功返回0,否则返回错误编号
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
pthread_mutex_t *restrict mutex: 锁的地址
const pthread_mutexattr_t *restrict attr: 锁的属性,默认写NULL
// 销毁互斥锁 返回:若成功返回0,否则返回错误编号
int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutex_t *mutex: 锁的地址,是一个指针
// 加锁 返回:若成功返回0,否则返回错误编号
int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_t *mutex: 锁的地址,是一个指针
// 解锁 返回:若成功返回0,否则返回错误编号
int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_t *mutex: 锁的地址,是一个指针
(死锁现象: 首先需要两把锁,当线程1先获得A锁后,想要再去获得B锁,这时线程2手里已经获得B锁后,想要再去获得A锁,这样线程1和线程2都想要拿到对方手里的那把锁,而且谁也不让着谁,谁都不肯去解锁)
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *thread1(void *); //线程函数
void *thread2(void *); //线程函数
int i=1;
int main(void){
pthread_t t_a; //新线程标识符
pthread_t t_b; //新线程标识符
pthread_create(&t_a,NULL,thread1,(void *)NULL);/*创建线程t_a*/
pthread_create(&t_b,NULL,thread2,(void *)NULL); /*创建线程t_b*/
pthread_join(t_a, NULL);/*等待线程t_a结束*/
pthread_join(t_b, NULL);/*等待线程t_b结束*/
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
exit(0);
}
void *thread1(void *junk){
for(i=1;i<=6;i++){
printf("djh: Line: %d, i = %d\n", __LINE__, i);
pthread_mutex_lock(&mutex);/*锁住互斥量*/
printf("thread1: lock %d\n", __LINE__);
if(i%3==0){
printf("thread1:signal 1 %d\n", __LINE__);
pthread_cond_signal(&cond); /*条件改变,发送信号,通知t_b进程*/
printf("thread1:signal 2 %d\n", __LINE__);
printf("%s will sleep 1s in Line: %d \n", __FUNCTION__, __LINE__);
sleep(1);
}
pthread_mutex_unlock(&mutex);/*解锁互斥量*/
printf("thread1: unlock %d\n", __LINE__);
printf("%s will sleep 1s in Line: %d \n\n", __FUNCTION__, __LINE__);
sleep(1);
}
}
void *thread2(void *junk){
while(i<6){
printf("djh: Line: %d, i = %d\n", __LINE__, i);
pthread_mutex_lock(&mutex);
printf("thread2: lock %d\n", __LINE__);
if(i%3!=0){
printf("thread2: wait 1 %d\n", __LINE__);
pthread_cond_wait(&cond,&mutex); /*解锁mutex,并等待cond改变*/
printf("thread2: wait 2 %d\n", __LINE__);
}
pthread_mutex_unlock(&mutex);
printf("thread2: unlock %d\n", __LINE__);
printf("%s will sleep 1s in Line: %d \n\n", __FUNCTION__, __LINE__);
sleep(1);
}
}
( 线程一先执行,获得mutex锁,打印,然后释放mutex锁,然后阻塞自己1秒。
线程二此时和线程一应该是并发的执行 ,这里是一个要点,为什么说是线程此时是并发的执行,因为此时不做任何干涉的话,是没有办法确定是线程一先获得执行还是线程二先获得执行,到底那个线程先获得执行,取决于操作系统的调度,想刻意的让线程二先执行,可以让线程一开始执行时,先sleep一秒。
这里并发执行的情况是,线程一先进入循环,然后获得锁,此时估计线程二执行,阻塞在
pthread_mutex_lock(&mutex);
这行语句中,直到线程一释放mutex锁
pthread_mutex_unlock(&mutex);/*解锁互斥量*/
然后线程二得已执行,获取metux锁,满足if条件,到
pthread_cond_wait (&cond,&mutex); /*等待*/
这里的线程二阻塞,不仅仅是等待cond变量发生改变,同时释放mutex锁 ,mutex锁释放后,线程一终于获得了mutex锁,得已继续运行,当线程一的if(i%3==0)的条件满足后,通过pthread_cond_signal() 发送信号,告诉等待cond的变量的线程(这个情景中是线程二),cond条件变量已经发生了改变。
但是此时线程二并没有立即得到运行 ,因为线程二还在等待mutex锁的释放,所以线程一继续往下走,直到线程一释放mutex锁,线程二才能停止等待,打印语句,然后往下走通过pthread_mutex_unlock(&mutex) 释放mutex锁,进入下一次循环。)
pthread_cond_broadcast
pthread_cond_broadcast作用:唤醒多个线程。 另外,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但开发中通常只需要一部分线程去做执行任务,所以其它的线程需要继续阻塞等待。(开发推荐:对 pthread_cond_wait() 使用while 循环来做条件判断)