pthread_join 线程停止等待函数没有调用
pthread_create 线程生成后,没有等子线程停止,主线程就先停止了。
主线程停止后,整个程序停止,子线程在没有printf的时候就被结束了。
结论:不是你没有看到结果,而是在子线程printf("..................\n");之前整个程序就已经停止了。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#define FALSE -1
#define TRUE 0
void *shuchu( void *my )
{
int j;
printf("..................\n");
}
int main()
{
int i = 0;
int rc = 0;
int ret1;
pthread_t p_thread1;
if(0!=(ret1 = pthread_create(&p_thread1, NULL, shuchu, NULL)))printf("sfdfsdfi\n");
printf("[%d]\n",p_thread1);
pthread_join(p_thread1, NULL);
return TRUE;
}
2. Linux 多线程编程(二)2019-08-10
三种专门用于线程同步的机制:POSIX信号量,互斥量和条件变量.
在Linux上信号量API有两组,一组是System V IPC信号量,即PV操作,另外就是POSIX信号量,POSIX信号量的名字都是以sem_开头.
phshared参数指定信号量的类型,若其值为0,就表示这个信号量是当前进程的局部信号量,否则该信号量可以在多个进程之间共享.value值指定信号量的初始值,一般与下面的sem_wait函数相对应.
其中比较重要的函数sem_wait函数会以原子操作的方式将信号量的值减一,如果信号量的值为零,则sem_wait将会阻塞,信号量的值可以在sem_init函数中的value初始化;sem_trywait函数是sem_wait的非阻塞版本;sem_post函数将以原子的操作对信号量加一,当信号量的值大于0时,其他正在调用sem_wait等待信号量的线程将被唤醒.
这些函数成功时返回0,失败则返回-1并设置errno.
生产者消费者模型:
生产者对应一个信号量:sem_t procer;
消费者对应一个信号量:sem_t customer;
sem_init(&procer,2)----生产者拥有资源,可以工作;
sem_init(&customer,0)----消费者没有资源,阻塞;
在访问公共资源前对互斥量设置(加锁),确保同一时间只有一个线程访问数据,在访问完成后再释放(解锁)互斥量.
互斥锁的运行方式:串行访问共享资源;
信号量的运行方式:并行访问共享资源;
互斥量用pthread_mutex_t数据类型表示,在使用互斥量之前,必须使用pthread_mutex_init函数对它进行初始化,注意,使用完毕后需调用pthread_mutex_destroy.
pthread_mutex_init用于初始化互斥锁,mutexattr用于指定互斥锁的属性,若为NULL,则表示默认属性。除了用这个函数初始化互斥所外,还可以用如下方式初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER。
pthread_mutex_destroy用于销毁互斥锁,以释放占用的内核资源,销毁一个已经加锁的互斥锁将导致不可预期的后果。
pthread_mutex_lock以原子操作给一个互斥锁加锁。如果目标互斥锁已经被加锁,则pthread_mutex_lock则被阻塞,直到该互斥锁占有者把它给解锁.
pthread_mutex_trylock和pthread_mutex_lock类似,不过它始终立即返回,而不论被操作的互斥锁是否加锁,是pthread_mutex_lock的非阻塞版本.当目标互斥锁未被加锁时,pthread_mutex_trylock进行加锁操作;否则将返回EBUSY错误码。注意:这里讨论的pthread_mutex_lock和pthread_mutex_trylock是针对普通锁而言的,对于其他类型的锁,这两个加锁函数会有不同的行为.
pthread_mutex_unlock以原子操作方式给一个互斥锁进行解锁操作。如果此时有其他线程正在等待这个互斥锁,则这些线程中的一个将获得它.
三个打印机轮流打印:
输出结果:
如果说互斥锁是用于同步线程对共享数据的访问的话,那么条件变量就是用于在线程之间同步共享数据的值.条件变量提供了一种线程之间通信的机制:当某个共享数据达到某个值时,唤醒等待这个共享数据的线程.
条件变量会在条件不满足的情况下阻塞线程.且条件变量和互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生.
其中pthread_cond_broadcast函数以广播的形式唤醒所有等待目标条件变量的线程,pthread_cond_signal函数用于唤醒一个等待目标条件变量线程.但有时候我们可能需要唤醒一个固定的线程,可以通过间接的方法实现:定义一个能够唯一标识目标线程的全局变量,在唤醒等待条件变量的线程前先设置该变量为目标线程,然后采用广播的方式唤醒所有等待的线程,这些线程被唤醒之后都检查该变量以判断是否是自己.
采用条件变量+互斥锁实现生产者消费者模型:
运行结果:
阻塞队列+生产者消费者
运行结果:
3. linux的Ubuntu 中有些函数man不到
man命令一般是原来查看查看linux命令的详细用法的。pthreadcond_init是一个编程语言的函数,不是linux命令。
关于这个函数的详情可参见:
http://linux.die.net/man/3/pthread_cond_init
前面已经指出“pthreadcond_init是一个编程语言的函数”,它是C/C++中的函数,不是linux系统中的命令,要查看这个函数的详细用法与说明,应该看C++的帮助文件。man命令是用来查看linux系统命令的。
就像你在DOS命令行下,不能通过“?”来查看VB的函数说明一样。
另外。Linux系统是由用C写的(早期的Unix可能不是),(听说windows也是),能够调用一些C函数没有什么奇怪的。
4. linux 多进程信号同步问题
朋友你好:希望能帮到你。互相学习。
线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。
1)互斥锁(mutex)
通过锁机制实现线程间的同步。同一时刻只允许一个线程执行一个关键部分的代码。
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex *mutex);
int pthread_mutex_destroy(pthread_mutex *mutex);
int pthread_mutex_unlock(pthread_mutex *
(1)先初始化锁init()或静态赋值pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIER
attr_t有:
PTHREAD_MUTEX_TIMED_NP:其余线程等待队列
PTHREAD_MUTEX_RECURSIVE_NP:嵌套锁,允许线程多次加锁,不同线程,解锁后重新竞争
PTHREAD_MUTEX_ERRORCHECK_NP:检错,与一同,线程请求已用锁,返回EDEADLK;
PTHREAD_MUTEX_ADAPTIVE_NP:适应锁,解锁后重新竞争
(2)加锁,lock,trylock,lock阻塞等待锁,trylock立即返回EBUSY
(3)解锁,unlock需满足是加锁状态,且由加锁线程解锁
(4)清除锁,destroy(此时锁必需unlock,否则返回EBUSY,//Linux下互斥锁不占用内存资源
示例代码
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
#include "iostream"
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int tmp;
void* thread(void *arg)
{
cout << "thread id is " << pthread_self() << endl;
pthread_mutex_lock(&mutex);
tmp = 12;
cout << "Now a is " << tmp << endl;
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
pthread_t id;
cout << "main thread id is " << pthread_self() << endl;
tmp = 3;
cout << "In main func tmp = " << tmp << endl;
if (!pthread_create(&id, NULL, thread, NULL))
{
cout << "Create thread success!" << endl;
}
else
{
cout << "Create thread failed!" << endl;
}
pthread_join(id, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
编译: g++ -o thread testthread.cpp -lpthread
说明:pthread库不是Linux系统默认的库,连接时需要使用静态库libpthread.a,所以在使用pthread_create()创建线程,以及调用pthread_atfork()函数建立fork处理程序时,需要链接该库。在编译中要加 -lpthread参数。
2)条件变量(cond)
利用线程间共享的全局变量进行同步的一种机制。条件变量上的基本操作有:触发条件(当条件变为 true 时);等待条件,挂起线程直到其他线程触发条件。
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞
(1)初始化.init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER(前者为动态初始化,后者为静态初始化);属性置为NULL
(2)等待条件成立.pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真,timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
(3)激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
(4)清除条件变量:destroy;无线程等待,否则返回EBUSY
对于
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
一定要在mutex的锁定区域内使用。
如果要正确的使用pthread_mutex_lock与pthread_mutex_unlock,请参考
pthread_cleanup_push和pthread_cleanup_pop宏,它能够在线程被cancel的时候正确的释放mutex!
另外,posix1标准说,pthread_cond_signal与pthread_cond_broadcast无需考虑调用线程是否是mutex的拥有者,也就是说,可以在lock与unlock以外的区域调用。如果我们对调用行为不关心,那么请在lock区域之外调用吧。
说明:
(1)pthread_cond_wait 自动解锁互斥量(如同执行了pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用CPU时间,直到条件变量被触发(变量为ture)。在调用 pthread_cond_wait之前,应用程序必须加锁互斥量。pthread_cond_wait函数返回前,自动重新对互斥量加锁(如同执行了pthread_lock_mutex)。
(2)互斥量的解锁和在条件变量上挂起都是自动进行的。因此,在条件变量被触发前,如果所有的线程都要对互斥量加锁,这种机制可保证在线程加锁互斥量和进入等待条件变量期间,条件变量不被触发。条件变量要和互斥量相联结,以避免出现条件竞争——个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件(条件满足信号有可能在测试条件和调用pthread_cond_wait函数(block)之间被发出,从而造成无限制的等待)。
(3)pthread_cond_timedwait 和 pthread_cond_wait 一样,自动解锁互斥量及等待条件变量,但它还限定了等待时间。如果在abstime指定的时间内cond未触发,互斥量mutex被重新加锁,且pthread_cond_timedwait返回错误 ETIMEDOUT。abstime 参数指定一个绝对时间,时间原点与 time 和 gettimeofday 相同:abstime = 0 表示 1970年1月1日00:00:00 GMT。
(4)pthread_cond_destroy 销毁一个条件变量,释放它拥有的资源。进入 pthread_cond_destroy 之前,必须没有在该条件变量上等待的线程。
(5)条件变量函数不是异步信号安全的,不应当在信号处理程序中进行调用。特别要注意,如果在信号处理程序中调用 pthread_cond_signal 或pthread_cond_boardcast 函数,可能导致调用线程死锁。
示例程序1
#include <stdio.h>
#include <pthread.h>
#include "stdlib.h"
#include "unistd.h"
pthread_mutex_t mutex;
pthread_cond_t cond;
void hander(void *arg)
{
free(arg);
(void)pthread_mutex_unlock(&mutex);
}
void *thread1(void *arg)
{
pthread_cleanup_push(hander, &mutex);
while(1)
{
printf("thread1 is running\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
printf("thread1 applied the condition\n");
pthread_mutex_unlock(&mutex);
sleep(4);
}
pthread_cleanup_pop(0);
}
void *thread2(void *arg)
{
while(1)
{
printf("thread2 is running\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
printf("thread2 applied the condition\n");
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
pthread_t thid1,thid2;
printf("condition variable study!\n");
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
pthread_create(&thid1,NULL,thread1,NULL);
pthread_create(&thid2,NULL,thread2,NULL);
sleep(1);
do
{
pthread_cond_signal(&cond);
}while(1);
sleep(20);
pthread_exit(0);
return 0;
}
示例程序2:
#include <pthread.h>
#include <unistd.h>
#include "stdio.h"
#include "stdlib.h"
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
struct node
{
int n_number;
struct node *n_next;
} *head = NULL;
/*[thread_func]*/
static void cleanup_handler(void *arg)
{
printf("Cleanup handler of second thread./n");
free(arg);
(void)pthread_mutex_unlock(&mtx);
}
static void *thread_func(void *arg)
{
struct node *p = NULL;
pthread_cleanup_push(cleanup_handler, p);
while (1)
{
//这个mutex主要是用来保证pthread_cond_wait的并发性
pthread_mutex_lock(&mtx);
while (head == NULL)
{
//这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何
//这里要有一个while (head == NULL)呢?因为pthread_cond_wait里的线
//程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。
//这个时候,应该让线程继续进入pthread_cond_wait
// pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,
//然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立
//而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源
//用这个流程是比较清楚的/*block-->unlock-->wait() return-->lock*/
pthread_cond_wait(&cond, &mtx);
p = head;
head = head->n_next;
printf("Got %d from front of queue/n", p->n_number);
free(p);
}
pthread_mutex_unlock(&mtx); //临界区数据操作完毕,释放互斥锁
}
pthread_cleanup_pop(0);
return 0;
}
int main(void)
{
pthread_t tid;
int i;
struct node *p;
//子线程会一直等待资源,类似生产者和消费者,但是这里的消费者可以是多个消费者,而
//不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大
pthread_create(&tid, NULL, thread_func, NULL);
sleep(1);
for (i = 0; i < 10; i++)
{
p = (struct node*)malloc(sizeof(struct node));
p->n_number = i;
pthread_mutex_lock(&mtx); //需要操作head这个临界资源,先加锁,
p->n_next = head;
head = p;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mtx); //解锁
sleep(1);
}
printf("thread 1 wanna end the line.So cancel thread 2./n");
//关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,子线程会在最近的取消点,退出
//线程,而在我们的代码里,最近的取消点肯定就是pthread_cond_wait()了。
pthread_cancel(tid);
pthread_join(tid, NULL);
printf("All done -- exiting/n");
return 0;
}
3)信号量
如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。
信号量函数的名字都以"sem_"打头。线程使用的基本信号量函数有四个。
#include <semaphore.h>
int sem_init (sem_t *sem , int pshared, unsigned int value);
这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux 只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。
两个原子操作函数:
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
这两个函数都要用一个由sem_init调用初始化的信号量对象的指针做参数。
sem_post:给信号量的值加1;
sem_wait:给信号量减1;对一个值为0的信号量调用sem_wait,这个函数将会等待直到有其它线程使它不再是0为止。
int sem_destroy(sem_t *sem);
这个函数的作用是再我们用完信号量后都它进行清理。归还自己占有的一切资源。
示例代码:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>
#define return_if_fail(p) if((p) == 0){printf ("[%s]:func error!/n", __func__);return;}
typedef struct _PrivInfo
{
sem_t s1;
sem_t s2;
time_t end_time;
}PrivInfo;
static void info_init (PrivInfo* thiz);
static void info_destroy (PrivInfo* thiz);
static void* pthread_func_1 (PrivInfo* thiz);
static void* pthread_func_2 (PrivInfo* thiz);
int main (int argc, char** argv)
{
pthread_t pt_1 = 0;
pthread_t pt_2 = 0;
int ret = 0;
PrivInfo* thiz = NULL;
thiz = (PrivInfo* )malloc (sizeof (PrivInfo));
if (thiz == NULL)
{
printf ("[%s]: Failed to malloc priv./n");
return -1;
}
info_init (thiz);
ret = pthread_create (&pt_1, NULL, (void*)pthread_func_1, thiz);
if (ret != 0)
{
perror ("pthread_1_create:");
}
ret = pthread_create (&pt_2, NULL, (void*)pthread_func_2, thiz);
if (ret != 0)
{
perror ("pthread_2_create:");
}
pthread_join (pt_1, NULL);
pthread_join (pt_2, NULL);
info_destroy (thiz);
return 0;
}
static void info_init (PrivInfo* thiz)
{
return_if_fail (thiz != NULL);
thiz->end_time = time(NULL) + 10;
sem_init (&thiz->s1, 0, 1);
sem_init (&thiz->s2, 0, 0);
return;
}
static void info_destroy (PrivInfo* thiz)
{
return_if_fail (thiz != NULL);
sem_destroy (&thiz->s1);
sem_destroy (&thiz->s2);
free (thiz);
thiz = NULL;
return;
}
static void* pthread_func_1 (PrivInfo* thiz)
{
return_if_fail (thiz != NULL);
while (time(NULL) < thiz->end_time)
{
sem_wait (&thiz->s2);
printf ("pthread1: pthread1 get the lock./n");
sem_post (&thiz->s1);
printf ("pthread1: pthread1 unlock/n");
sleep (1);
}
return;
}
static void* pthread_func_2 (PrivInfo* thiz)
{
return_if_fail (thiz != NULL);
while (time (NULL) < thiz->end_time)
{
sem_wait (&thiz->s1);
printf ("pthread2: pthread2 get the unlock./n");
sem_post (&thiz->s2);
printf ("pthread2: pthread2 unlock./n");
sleep (1);
}
return;
}
通 过执行结果后,可以看出,会先执行线程二的函数,然后再执行线程一的函数。它们两就实现了同步
5. Linux 线程同步有哪些方法
一、互斥锁(mutex)
1.
初始化锁。在Linux下,线程的互斥量数据类型是pthread_mutex_t。在使用前,要对它进行初始化。
静态分配:pthread_mutex_t
mutex
=
PTHREAD_MUTEX_INITIALIZER;
动态分配:int
pthread_mutex_init(pthread_mutex_t
*mutex,
const
pthread_mutex_attr_t
*mutexattr);
2.
加锁。对共享资源的访问,要对互斥量进行加锁,如果互斥量已经上了锁,调用线程会阻塞,直到互斥量被解锁。
int
pthread_mutex_lock(pthread_mutex
*mutex);
int
pthread_mutex_trylock(pthread_mutex_t
*mutex);
3.
解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。
int
pthread_mutex_unlock(pthread_mutex_t
*mutex);
4.
销毁锁。锁在是使用完成后,需要进行销毁以释放资源。
int
pthread_mutex_destroy(pthread_mutex
*mutex);
二、条件变量(cond)
1.
初始化条件变量。
静态态初始化,pthread_cond_t
cond
=
PTHREAD_COND_INITIALIER;
动态初始化,int
pthread_cond_init(pthread_cond_t
*cond,
pthread_condattr_t
*cond_attr);
2.
等待条件成立。释放锁,同时阻塞等待条件变量为真才行。timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
int
pthread_cond_wait(pthread_cond_t
*cond,
pthread_mutex_t
*mutex);
int
pthread_cond_timewait(pthread_cond_t
*cond,pthread_mutex
*mutex,const
timespec
*abstime);
3.
激活条件变量。pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
int
pthread_cond_signal(pthread_cond_t
*cond);
int
pthread_cond_broadcast(pthread_cond_t
*cond);
//解除所有线程的阻塞
4.
清除条件变量。无线程等待,否则返回EBUSY
int
pthread_cond_destroy(pthread_cond_t
*cond);
三、信号量(sem)
1.
信号量初始化。
int
sem_init
(sem_t
*sem
,
int
pshared,
unsigned
int
value);
这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux
只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。
2.
等待信号量。给信号量减1,然后等待直到信号量的值大于0。
int
sem_wait(sem_t
*sem);
3.
释放信号量。信号量值加1。并通知其他等待线程。
int
sem_post(sem_t
*sem);
4.
销毁信号量。我们用完信号量后都它进行清理。归还占有的一切资源。
int
sem_destroy(sem_t
*sem);
6. Linux C语言pthread_mutex_unlock和pthread_cond_signal顺序问题
signal前解锁是错误,顺序相当重要,锁才能保证你整个操作是完全原子,signal只是整个操作的一部分,它不能被分割出去。用特殊一点的情况来解释:如果解锁后才signal,那么有可能信号一直发不出去,因为信号线程一直得到不调度。
7. 浅谈linux 多线程编程和 windows 多线程编程的异同
首先我们讲讲为什么要采用多线程编程,其实并不是所有的程序都必须采用多线程,有些时候采用多线程,性能还没有单线程好。所以我们要搞清楚,什么时候采用多线程。采用多线程的好处如下:
(1)因为多线程彼此之间采用相同的地址空间,共享大部分的数据,这样和多进程相比,代价比较节俭,因为多进程的话,启动新的进程必须分配给它独立的地址空间,这样需要数据表来维护代码段,数据段和堆栈段等等。
(2)多线程和多进程相比,一个明显的优点就是线程之间的通信了,对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。但是对于多线程就不一样了。他们之间可以直接共享数据,比如最简单的方式就是共享全局变量。但是共享全部变量也要注意哦,呵呵,必须注意同步,不然后果你知道的。呵呵。
(3)在多cpu的情况下,不同的线程可以运行不同的cpu下,这样就完全并行了。
反正我觉得在这种情况下,采用多线程比较理想。比如说你要做一个任务分2个步骤,你为提高工作效率,你可以多线程技术,开辟2个线程,第一个线程就做第一步的工作,第2个线程就做第2步的工作。但是你这个时候要注意同步了。因为只有第一步做完才能做第2步的工作。这时,我们可以采用同步技术进行线程之间的通信。
针对这种情况,我们首先讲讲多线程之间的通信,在windows平台下,多线程之间通信采用的方法主要有:
(1)共享全局变量,这种方法是最容易想到的,呵呵,那就首先讲讲吧,比如说吧,上面的问题,第一步要向第2步传递收据,我们可以之间共享全局变量,让两个线程之间传递数据,这时主要考虑的就是同步了,因为你后面的线程在对数据进行操作的时候,你第一个线程又改变了数据的内容,你不同步保护,后果很严重的。你也知道,这种情况就是读脏数据了。在这种情况下,我们最容易想到的同步方法就是设置一个bool flag了,比如说在第2个线程还没有用完数据前,第一个线程不能写入。有时在2个线程所需的时间不相同的时候,怎样达到最大效率的同步,就比较麻烦了。咱们可以多开几个缓冲区进行操作。就像生产者消费者一样了。如果是2个线程一直在跑的,由于时间不一致,缓冲区迟早会溢出的。在这种情况下就要考虑了,是不让数据写入还是让数据覆盖掉老的数据,这时候就要具体问题具体分析了。就此打住,呵呵。就是用bool变量控制同步,linux 和windows是一样的。
既然讲道了这里,就再讲讲其它同步的方法。同样 针对上面的这个问题,共享全局变量同步问题。除了采用bool变量外,最容易想到的方法就是互斥量了。呵呵,也就是传说中的加锁了。windows下加锁和linux下加锁是类似的。采用互斥量进行同步,要想进入那段代码,就先必须获得互斥量。
linux上互斥量的函数是:
windows下互斥量的函数有:createmutex 创建一个互斥量,然后就是获得互斥量waitforsingleobject函数,用完了就释放互斥量ReleaseMutex(hMutex),当减到0的时候 内核会才会释放其对象。下面是windows下与互斥的几个函数原型。
HANDLE WINAPI CreateMutex(
__in LPSECURITY_ATTRIBUTES lpMutexAttributes,
__in BOOL bInitialOwner,
__in LPCTSTR lpName
);
可以可用来创建一个有名或无名的互斥量对象
第一参数 可以指向一个结构体SECURITY_ATTRIBUTES一般可以设为null;
第二参数 指当时的函数是不是感应感应状态 FALSE为当前拥有者不会创建互斥
第三参数 指明是否是有名的互斥对象 如果是无名 用null就好。
DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,
__in DWORD dwMilliseconds
);
第一个是 创建的互斥对象的句柄。第二个是 表示将在多少时间之后返回 如果设为宏INFINITE 则不会返回 直到用户自己定义返回。
对于linux操作系统,互斥也是类似的,只是函数不同罢了。在linux下,和互斥相关的几个函数也要闪亮登场了。
pthread_mutex_init函数:初始化一个互斥锁;
pthread_mutex_destroy函数:注销一个互斥锁;
pthread_mutex_lock函数:加锁,如果不成功,阻塞等待;
pthread_mutex_unlock函数:解锁;
pthread_mutex_trylock函数:测试加锁,如果不成功就立即返回,错误码为EBUSY;
至于这些函数的用法,google上一搜,就出来了,呵呵,在这里不多讲了。windows下还有一个可以用来保护数据的方法,也是线程同步的方式
就是临界区了。临界区和互斥类似。它们之间的区别是,临界区速度快,但是它只能用来同步同一个进程内的多个线程。临界区的获取和释放函数如下:
EnterCriticalSection() 进入临界区; LeaveCriticalSection()离开临界区。 对于多线程共享内存的东东就讲到这里了。
(2)采用消息机制进行多线程通信和同步,windows下面的的消息机制的函数用的多的就是postmessage了。Linux下的消息机制,我用的较少,就不在这里说了,如果谁熟悉的,也告诉我,呵呵。
(3)windows下的另外一种线程通信方法就是事件和信号量了。同样针对我开始举得例子,2个线程同步,他们之间传递信息,可以采用事件(Event)或信号量(Semaphore),比如第一个线程完成生产的数据后,就必须告诉第2个线程,他已经把数据准备好了,你可以来取走了。第2个线程就把数据取走。呵呵,这里可以采用消息机制,当第一个线程准备好数据后,就直接postmessage给第2个线程,按理说采用postmessage一个线程就可以搞定这个问题了。呵呵,不是重点,省略不讲了。
对于linux,也有类似的方法,就是条件变量了,呵呵,这里windows和linux就有不同了。要特别讲讲才行。
对于windows,采用事件和信号量同步时候,都会使用waitforsingleobject进行等待的,这个函数的第一个参数是一个句柄,在这里可以是Event句柄,或Semaphore句柄,第2个参数就是等待的延迟,最终等多久,单位是ms,如果这个参数为INFINITE,那么就是无限等待了。释放信号量的函数为ReleaseSemaphore();释放事件的函数为SetEvent。当然使用这些东西都要初始化的。这里就不讲了。Msdn一搜,神马都出来了,呵呵。神马都是浮云!
对于linux操作系统,是采用条件变量来实现类似的功能的。Linux的条件变量一般都是和互斥锁一起使用的,主要的函数有:
pthread_mutex_lock ,
pthread_mutex_unlock,
pthread_cond_init
pthread_cond_signal
pthread_cond_wait
pthread_cond_timewait
为了和windows操作系统进行对比,我用以下表格进行比较:
对照以上表格,总结如下:
(1) Pthread_cleanup_push,Pthread_cleanup_pop:
这一对函数push和pop的作用是当出现异常退出时,做一些清除操作,即当在push和pop函数之间异常退出,包括调用pthread_exit退出,都会执行push里面的清除函数,如果有多个push,注意是是栈,先执行后面的那个函数,在执行前面的函数,但是注意当在这2个函数之间通过return 退出的话,执不执行push后的函数就看pop函数中的参数是不是为0了。还有当没有异常退出时,等同于在这里面return退出的情况,即:当pop函数参数不为0时,执行清除操作,当pop函数参数为0时,不执行push函数中的清除函数。
(2)linux的pthread_cond_signal和SetEvent的不同点
Pthread_cond_singal释放信号后,当没有Pthread_cond_wait,信号马上复位了,这点和SetEvent不同,SetEvent是不会复位的。详解如下:
条件变量的置位和复位有2种常用模型:第一种模型是当条件变量置位时(signaled)以后,如果当前没有线程在等待,其状态会保持为置位(signaled),直到有等待的线程进入被触发,其状态才会变为unsignaled,这种模型以采用Windows平台上的Auto-set Event 为代表。
第2种模型则是Linux平台的pthread所采用的模型,当条件变量置位(signaled)以后,即使当前没有任何线程在等待,其状态也会恢复为复位(unsignaled)状态。
条件变量在Linux平台上的这种模型很难说好坏,在实际应用中,我们可以对
代码稍加改进就可以避免这种差异的发生。由于这种差异只会发生在触发没有被线程等待在条件变量的时刻,因此我们只需要掌握好触发的时机即可。最简单的做法是增加一个计数器记录等待线程的个数,在决定触发条件变量前检查该变量即可。
示例 使用 pthread_cond_wait() 和 pthread_cond_signal()
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count()
{
pthread_mutex_lock(&count_lock);
while (count == 0)
pthread_cond_wait(&count_nonzero, &count_lock);
count = count - 1;
pthread_mutex_unlock(&count_lock);
}
increment_count()
{
pthread_mutex_lock(&count_lock);
if (count == 0)
pthread_cond_signal(&count_nonzero);
count = count + 1;
pthread_mutex_unlock(&count_lock);
}
(3) 注意Pthread_cond_wait条件返回时互斥锁的解锁问题
extern int pthread_cond_wait __P ((pthread_cond_t *__cond,pthread_mutex_t *__mutex));
调用这个函数时,线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数 pthread_cond_broadcast唤醒线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。如果在多线程中采用pthread_cond_wait来等待时,会首先释放互斥锁,当等待的信号到来时,再次获得互斥锁,因此在之后要注意手动解锁。举例如下:
#include
#include
#include
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_b, NULL);/*等待进程t_b结束*/
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
exit(0);
}
void *thread1(void *junk)
{
for(i=1;i<=9;i++)
{
printf("IN one\n");
pthread_mutex_lock(&mutex);//
if(i%3==0)
pthread_cond_signal(&cond);/*,发送信号,通知t_b进程*/
else
printf("thead1:%d\n",i);
pthread_mutex_unlock(&mutex);//*解锁互斥量*/
printf("Up Mutex\n");
sleep(3);
}
}
void *thread2(void *junk)
{
while(i<9)
{
printf("IN two \n");
pthread_mutex_lock(&mutex);
if(i%3!=0)
pthread_cond_wait(&cond,&mutex);/*等待*/
printf("thread2:%d\n",i);
pthread_mutex_unlock(&mutex);
printf("Down Mutex\n");
sleep(3);
}
}
输出如下:
IN one
thead1:1
Up Mutex
IN two
IN one
thead1:2
Up Mutex
IN one
thread2:3
Down Mutex
Up Mutex
IN one
thead1:4
Up Mutex
IN two
IN one
thead1:5
Up Mutex
IN one
Up Mutex
thread2:6
Down Mutex
IN two
thread2:6
Down Mutex
IN one
thead1:7
Up Mutex
IN one
thead1:8
Up Mutex
IN two
IN one
Up Mutex
thread2:9
Down Mutex
注意蓝色的地方,有2个thread2:6,其实当这个程序多执行几次,i=3和i=6时有可能多打印几个,这里就是竞争锁造成的了。
(4)另外要注意的Pthread_cond_timedwait等待的是绝对时间,这个和WaitForSingleObject是不同的,Pthread_cond_timedwait在网上也有讨论。如下:这个问题比较经典,我把它搬过来。
thread_a :
pthread_mutex_lock(&mutex);
//do something
pthread_mutex_unlock(&mutex)
thread_b:
pthread_mutex_lock(&mutex);
//do something
pthread_cond_timedwait(&cond, &mutex, &tm);
pthread_mutex_unlock(&mutex)
有如上两个线程thread_a, thread_b,现在如果a已经进入了临界区,而b同时超时了,那么b会从pthread_cond_timedwait返回吗?如果能返回,那岂不是a,b都在临界区?如果不能返回,那pthread_cond_timedwait的定时岂不是就不准了?
大家讨论有价值的2点如下:
(1) pthread_cond_timedwait (pthread_cond_t *cv, pthread_mutex_t *external_mutex, const struct timespec *abstime) -- This function is a time-based variant of pthread_cond_wait. It waits up to abstime amount of time for cv to be notified. If abstime elapses before cv is notified, the function returns back to the caller with an ETIME result, signifying that a timeout has occurred. Even in the case of timeouts, the external_mutex will be locked when pthread_cond_timedwait returns.
(2) 2.1 pthread_cond_timedwait行为和pthread_cond_wait一样,在返回的时候都要再次lock mutex.
2 .2pthread_cond_timedwait所谓的如果没有等到条件变量,超时就返回,并不确切。
如果pthread_cond_timedwait超时到了,但是这个时候不能lock临界区,pthread_cond_timedwait并不会立即返回,但是在pthread_cond_timedwait返回的时候,它仍在临界区中,且此时返回值为ETIMEDOUT。
关于pthread_cond_timedwait超时返回的问题,我也认同观点2。
附录:
int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);
返回值:若成功则返回0,否则返回出错编号
返回成功时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于制定各种不同的线程属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。
linux下用C开发多线程程序,Linux系统下的多线程遵循POSIX线程接口,称为pthread。
由 restrict 修饰的指针是最初唯一对指针所指向的对象进行存取的方法,仅当第二个指针基于第一个时,才能对对象进行存取。对对象的存取都限定于基于由 restrict 修饰的指针表达式中。 由 restrict 修饰的指针主要用于函数形参,或指向由 malloc() 分配的内存空间。restrict 数据类型不改变程序的语义。 编译器能通过作出 restrict 修饰的指针是存取对象的唯一方法的假设,更好地优化某些类型的例程。
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
第四个参数是运行函数的参数。
因为pthread不是linux系统的库,所以在编译时注意加上-lpthread参数,以调用静态链接库。
终止线程:
如果在进程中任何一个线程中调用exit或_exit,那么整个进行会终止,线程正常的退出方式有:
(1) 线程从启动例程中返回(return)
(2) 线程可以被另一个进程终止(kill);
(3) 线程自己调用pthread_exit函数
#include
pthread_exit
线程等待:
int pthread_join(pthread_t tid,void **rval_ptr)
函数pthread_join用来等待一个线程的结束。函数原型为:
extern int pthread_join __P (pthread_t __th, void **__thread_return);
第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。
对于windows线程的创建东西,就不列举了,msdn上 一搜就出来了。呵呵。今天就讲到这里吧,希望是抛砖引玉,大家一起探讨,呵呵。部分内容我也是参考internet的,特此对原作者表示感谢!