linux多线程同步 机制_linux多线程同步机制 - CSDN
  • linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。 一、互斥锁(mutex) 通过锁机制实现线程间的同步。 初始化锁。在Linux下,线程的互斥量数据类型是pthread_mutex_t。在使用前,要对...

    线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。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);
    #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
    

    二、条件变量(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);
    #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;
    }
    #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;
    
    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);,再读取资源
    			//用这个流程是比较清楚的
    			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;
    }

    三、信号量(sem)

    如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。信号量函数的名字都以"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);
    #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;
    }

    展开全文
  • Linux多线程同步机制

    2011-10-16 19:46:03
    尽管在Posix Thread中同样可以使用IPC的信号量机制来实现互斥锁mutex功能,但显然semphore的功能过于强大了,在Posix Thread中定义了另外一套专门用于线程同步的mutex函数。 1. 创建和销毁  有两种方法创建互斥...
     一、互斥锁
    

    尽管在Posix Thread中同样可以使用IPC的信号量机制来实现互斥锁mutex功能,但显然semphore的功能过于强大了,在Posix Thread中定义了另外一套专门用于线程同步的mutex函数。

    1. 创建和销毁

       有两种方法创建互斥锁,静态方式和动态方式。

       POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下: pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; 在LinuxThreads实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。

       动态方式是采用pthread_mutex_init()函数来初始化互斥锁,API定义如下: int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) 其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。

       pthread_mutex_destroy()用于注销一个互斥锁,API定义如下: int pthread_mutex_destroy(pthread_mutex_t *mutex) 销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。

    2. 互斥锁属性

       互斥锁的属性在创建锁的时候指定,在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,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。


    3. 锁操作

       锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。

    int pthread_mutex_lock(pthread_mutex_t *mutex)
    int pthread_mutex_unlock(pthread_mutex_t *mutex)
    int pthread_mutex_trylock(pthread_mutex_t *mutex)

    pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。

    4. 其他

       POSIX线程锁机制的Linux实现都不是取消点,因此,延迟取消类型的线程不会因收到取消信号而离开加锁等待。值得注意的是,如果线程在加锁后解锁前被取消,锁将永远保持锁定状态,因此如果在关键区段内有取消点存在,或者设置了异步取消类型,则必须在退出回调函数中解锁。

       这个锁机制同时也不是异步信号安全的,也就是说,不应该在信号处理过程中使用互斥锁,否则容易造成死锁。


     

    二、条件变量

       条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。

    1. 创建和注销

    条件变量和互斥锁一样,都有静态动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER常量,如下:
    pthread_cond_t cond=PTHREAD_COND_INITIALIZER

    动态方式调用pthread_cond_init()函数,API定义如下:
    int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)

    尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。

       注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。API定义如下:
    int pthread_cond_destroy(pthread_cond_t *cond)

    2. 等待和激发

    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)

     

       等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。

       无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求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()则激活所有等待线程。

    3. 其他

    pthread_cond_wait()和pthread_cond_timedwait()都被实现为取消点,因此,在该处等待的线程将立即重新运行,在重新锁定mutex后离开pthread_cond_wait(),然后执行取消动作。也就是说如果pthread_cond_wait()被取消,mutex是保持锁定状态的,因而需要定义退出回调函数来为其解锁。

    以下示例集中演示了互斥锁和条件变量的结合使用,以及取消对于条件等待动作的影响。在例子中,有两个线程被启动,并等待同一个条件变量,如果不使用退出回调函数(见范例中的注释部分),则tid2将在pthread_mutex_lock()处永久等待。如果使用回调函数,则tid2的条件等待及主线程的条件激发都能正常工作。

    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    pthread_mutex_t mutex;
    pthread_cond_t  cond;
    void * child1(void *arg)
    {
            pthread_cleanup_push(pthread_mutex_unlock,&mutex);  /* comment 1 */
            while(1){
                    printf("thread 1 get running \n");
            printf("thread 1 pthread_mutex_lock returns %d\n",
    pthread_mutex_lock(&mutex));
            pthread_cond_wait(&cond,&mutex);
                        printf("thread 1 condition applied\n");
            pthread_mutex_unlock(&mutex);
                        sleep(5);
        }
            pthread_cleanup_pop(0);     /* comment 2 */
    }
    void *child2(void *arg)
    {
            while(1){
                    sleep(3);               /* comment 3 */
                    printf("thread 2 get running.\n");
            printf("thread 2 pthread_mutex_lock returns %d\n",
    pthread_mutex_lock(&mutex));
            pthread_cond_wait(&cond,&mutex);
            printf("thread 2 condition applied\n");
            pthread_mutex_unlock(&mutex);
            sleep(1);
            }
    }
    int main(void)
    {
            int tid1,tid2;
            printf("hello, condition variable test\n");
            pthread_mutex_init(&mutex,NULL);
            pthread_cond_init(&cond,NULL);
            pthread_create(&tid1,NULL,child1,NULL);
            pthread_create(&tid2,NULL,child2,NULL);
            do{
            sleep(2);                   /* comment 4 */
                    pthread_cancel(tid1);       /* comment 5 */
                    sleep(2);                   /* comment 6 */
            pthread_cond_signal(&cond);
        }while(1); 
            sleep(100);
            pthread_exit(0);
    }

     

       如果不做注释5的pthread_cancel()动作,即使没有那些sleep()延时操作,child1和child2都能正常工作。注释3和注释4的延迟使得child1有时间完成取消动作,从而使child2能在child1退出之后进入请求锁操作。如果没有注释1和注释2的回调函数定义,系统将挂起在child2请求锁的地方;而如果同时也不做注释3和注释4的延时,child2能在child1完成取消动作以前得到控制,从而顺利执行申请锁的操作,但却可能挂起在pthread_cond_wait()中,因为其中也有申请mutex的操作。child1函数给出的是标准的条件变量的使用方式:回调函数保护,等待条件前锁定,pthread_cond_wait()返回后解锁。

    条件变量机制不是异步信号安全的,也就是说,在信号处理函数中调用pthread_cond_signal()或者pthread_cond_broadcast()很可能引起死锁。


     

    三、信号灯

       信号灯与互斥锁和条件变量的主要不同在于"灯"的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于"等待"操作,即资源不可用的话,信号灯机制则侧重于点灯,即告知资源可用;没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保持灯亮状态。当然,这样的操作原语也意味着更多的开销。

       信号灯的应用除了灯亮/灯灭这种二元灯以外,也可以采用大于1的灯数,以表示资源数大于1,这时可以称之为多元灯。

    1. 创建和注销

       POSIX信号灯标准定义了有名信号灯和无名信号灯两种,但LinuxThreads的实现仅有无名灯,同时有名灯除了总是可用于多进程之间以外,在使用上与无名灯并没有很大的区别,因此下面仅就无名灯进行讨论。

    int sem_init(sem_t *sem, int pshared, unsigned int value)
    这是创建信号灯的API,其中value为信号灯的初值,pshared表示是否为多进程共享而不仅仅是用于一个进程。LinuxThreads没有实现多进程共享信号灯,因此所有非0值的pshared输入都将使sem_init()返回-1,且置errno为ENOSYS。初始化好的信号灯由sem变量表征,用于以下点灯、灭灯操作。

    int sem_destroy(sem_t * sem)
    被注销的信号灯sem要求已没有线程在等待该信号灯,否则返回-1,且置errno为EBUSY。除此之外,LinuxThreads的信号灯注销函数不做其他动作。

    2. 点灯和灭灯

    int sem_post(sem_t * sem)

    点灯操作将信号灯值原子地加1,表示增加一个可访问的资源。

    int sem_wait(sem_t * sem)
    int sem_trywait(sem_t * sem)
     

    sem_wait()为等待灯亮操作,等待灯亮(信号灯值大于0),然后将信号灯原子地减1,并返回。sem_trywait()为sem_wait()的非阻塞版,如果信号灯计数大于0,则原子地减1并返回0,否则立即返回-1,errno置为EAGAIN。

    3. 获取灯值

    int sem_getvalue(sem_t * sem, int * sval)

    读取sem中的灯计数,存于*sval中,并返回0。

    4. 其他

    sem_wait()被实现为取消点,而且在支持原子"比较且交换"指令的体系结构上,sem_post()是唯一能用于异步信号处理函数的POSIX异步信号安全的API。


     

    四、异步信号

       由于LinuxThreads是在核外使用核内轻量级进程实现的线程,所以基于内核的异步信号操作对于线程也是有效的。但同时,由于异步信号总是实际发往某个进程,所以无法实现POSIX标准所要求的"信号到达某个进程,然后再由该进程将信号分发到所有没有阻塞该信号的线程中"原语,而是只能影响到其中一个线程。

       POSIX异步信号同时也是一个标准C库提供的功能,主要包括信号集管理(sigemptyset()、sigfillset()、sigaddset()、sigdelset()、sigismember()等)、信号处理函数安装(sigaction())、信号阻塞控制(sigprocmask())、被阻塞信号查询(sigpending())、信号等待(sigsuspend())等,它们与发送信号的kill()等函数配合就能实现进程间异步信号功能。LinuxThreads围绕线程封装了sigaction()何raise(),本节集中讨论LinuxThreads中扩展的异步信号函数,包括pthread_sigmask()、pthread_kill()和sigwait()三个函数。毫无疑问,所有POSIX异步信号函数对于线程都是可用的。

    int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask)
    设置线程的信号屏蔽码,语义与sigprocmask()相同,但对不允许屏蔽的Cancel信号和不允许响应的Restart信号进行了保护。被屏蔽的信号保存在信号队列中,可由sigpending()函数取出。

    int pthread_kill(pthread_t thread, int signo)
    向thread号线程发送signo信号。实现中在通过thread线程号定位到对应进程号以后使用kill()系统调用完成发送。

    int sigwait(const sigset_t *set, int *sig)
    挂起线程,等待set中指定的信号之一到达,并将到达的信号存入*sig中。POSIX标准建议在调用sigwait()等待信号以前,进程中所有线程都应屏蔽该信号,以保证仅有sigwait()的调用者获得该信号,因此,对于需要等待同步的异步信号,总是应该在创建任何线程以前调用pthread_sigmask()屏蔽该信号的处理。而且,调用sigwait()期间,原来附接在该信号上的信号处理函数不会被调用。

    如果在等待期间接收到Cancel信号,则立即退出等待,也就是说sigwait()被实现为取消点。


     

    五、其他同步方式

       除了上述讨论的同步方式以外,其他很多进程间通信手段对于LinuxThreads也是可用的,比如基于文件系统的IPC(管道、Unix域Socket等)、消息队列(Sys.V或者Posix的)、System V的信号灯等。只有一点需要注意,LinuxThreads在核内是作为共享存储区、共享文件系统属性、共享信号处理、共享文件描述符的独立进程看待的。

     

    条件变量与互斥锁、信号量的区别

    1).互斥锁必须总是由给它上锁的线程解锁,信号量的挂出即不必由执行过它的等待操作的同一进程执行。一个线程可以等待某个给定信号灯,而另一个线程可以挂出该信号灯。

    2).互斥锁要么锁住,要么被解开(二值状态,类型二值信号量)

    3).由于信号量有一个与之关联的状态(它的计数值),信号量挂出操作总是被记住。然而当向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么该信号将丢失。

    4).互斥锁是为了上锁而设计的,条件变量是为了等待而设计的,信号灯即可用于上锁,也可用于等待,因而可能导致更多的开销和更高的复杂性。

     

    展开全文
  • 相似地,线程同步是控制线程执行和访问临界区域的方法。 一、什么是信号量 线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。...
    信号量、同步这些名词在进程间通信时就已经说过,在这里它们的意思是相同的,只不过是同步的对象不同而已。但是下面介绍的信号量的接口是用于线程的信号量,注意不要跟用于进程间通信的信号量混淆,关于用于进程间通信的信号量的详细介绍可以参阅我的另一篇博文:Linux进程间通信——使用信号量。相似地,线程同步是控制线程执行和访问临界区域的方法

    一、什么是信号量
    线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。

    而只有0和1两种取值的信号量叫做二进制信号量,在这里将重点介绍。而信号量一般常用于保护一段代码,使其每次只被一个执行线程运行。我们可以使用二进制信号量来完成这个工作。

    二、信号量的接口和使用

    信号量的函数都以sem_开头,线程中使用的基本信号量函数有4个,它们都声明在头文件semaphore.h中。

    1、sem_init函数
    该函数用于创建信号量,其原型如下:
    int sem_init(sem_t *sem, int pshared, unsigned int value);
    该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value为sem的初始值。调用成功时返回0,失败返回-1.

    2、sem_wait函数
    该函数用于以原子操作的方式将信号量的值减1。原子操作就是,如果两个线程企图同时给一个信号量加1或减1,它们之间不会互相干扰。它的原型如下:
    int sem_wait(sem_t *sem);
    sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.

    3、sem_post函数
    该函数用于以原子操作的方式将信号量的值加1。它的原型如下:
    int sem_post(sem_t *sem);
    与sem_wait一样,sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.

    4、sem_destroy函数
    该函数用于对用完的信号量的清理。它的原型如下:
    int sem_destroy(sem_t *sem);
    成功时返回0,失败时返回-1.

    三、使用信号量同步线程

    下面以一个简单的多线程程序来说明如何使用信号量进行线程同步。在主线程中,我们创建子线程,并把数组msg作为参数传递给子线程,然后主线程等待直到有文本输入,然后调用sem_post来增加信号量的值,这样就会立刻使子线程从sem_wait的等待中返回并开始执行。线程函数在把字符串的小写字母变成大写并统计输入的字符数量之后,它再次调用sem_wait并再次被阻塞,直到主线程再次调用sem_post增加信号量的值。

    #include <unistd.h>
    #include <pthread.h>
    #include <semaphore.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    //线程函数
    void *thread_func(void *msg);
    sem_t sem;//信号量
    
    #define MSG_SIZE 512
    
    int main()
    {
    	int res = -1;
    	pthread_t thread;
    	void *thread_result = NULL;
    	char msg[MSG_SIZE];
    	//初始化信号量,其初值为0
    	res = sem_init(&sem, 0, 0);
    	if(res == -1)
    	{
    		perror("semaphore intitialization failed\n");
    		exit(EXIT_FAILURE);
    	}
    	//创建线程,并把msg作为线程函数的参数
    	res = pthread_create(&thread, NULL, thread_func, msg);
    	if(res != 0)
    	{
    		perror("pthread_create failed\n");
    		exit(EXIT_FAILURE);
    	}
    	//输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n”
    	printf("Input some text. Enter 'end'to finish...\n");
    	while(strcmp("end\n", msg) != 0)
    	{
    		fgets(msg, MSG_SIZE, stdin);
    		//把信号量加1
    		sem_post(&sem);
    	}
    
    	printf("Waiting for thread to finish...\n");
    	//等待子线程结束
    	res = pthread_join(thread, &thread_result);
    	if(res != 0)
    	{
    		perror("pthread_join failed\n");
    		exit(EXIT_FAILURE);
    	}
    	printf("Thread joined\n");
    	//清理信号量
    	sem_destroy(&sem);
    	exit(EXIT_SUCCESS);
    }
    
    void* thread_func(void *msg)
    {
    	//把信号量减1
    	sem_wait(&sem);
    	char *ptr = msg;
    	while(strcmp("end\n", msg) != 0)
    	{
    		int i = 0;
    		//把小写字母变成大写
    		for(; ptr[i] != '\0'; ++i)
    		{
    			if(ptr[i] >= 'a' && ptr[i] <= 'z')
    			{
    				ptr[i] -= 'a' - 'A';
    			}
    		}
    		printf("You input %d characters\n", i-1);
    		printf("To Uppercase: %s\n", ptr);
    		//把信号量减1
    		sem_wait(&sem);
    	}
    	//退出线程
    	pthread_exit(NULL);
    }
    运行结果如下:



    从运行的结果来看,这个程序的确是同时在运行两个线程,一个控制输入,另一个控制处理统计和输出。

    四、分析此信号量同步程序的缺陷
    但是这个程序有一点点的小问题,就是这个程序依赖接收文本输入的时间足够长,这样子线程才有足够的时间在主线程还未准备好给它更多的单词去处理和统计之前处理和统计出工作区中字符的个数。所以当我们连续快速地给它两组不同的单词去统计时,子线程就没有足够的时间支执行,但是信号量已被增加不止一次,所以字符统计线程(子线程)就会反复处理和统计字符数目,并减少信号量的值,直到它再次变成0为止。

    为了更加清楚地说明上面所说的情况,修改主线程的while循环中的代码,如下:
    	printf("Input some text. Enter 'end'to finish...\n");
    	while(strcmp("end\n", msg) != 0)
    	{
    		if(strncmp("TEST", msg, 4) == 0)
    		{
    			strcpy(msg, "copy_data\n");
    			sem_post(&sem);
    		}
    		fgets(msg, MSG_SIZE, stdin);
    		//把信号量加1
    		sem_post(&sem);
    	}
    重新编译程序,此时运行结果如下:



    当我们输入TEST时,主线程向子线程提供了两个输入,一个是来自键盘的输入,一个来自主线程复数据到msg中,然后从运行结果可以看出,运行出现了异常,没有处理和统计从键盘输入TEST的字符串而却对复制的数据作了两次处理。原因如上面所述。

    五、解决此缺陷的方法

    解决方法有两个,一个就是再增加一个信号量,让主线程等到子线程处理统计完成之后再继续执行;另一个方法就是使用互斥量。

    下面给出用增加一个信号量的方法来解决该问题的代码,源文件名为semthread2.c,源代码如下:
    #include <unistd.h>
    #include <pthread.h>
    #include <semaphore.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    
    //线程函数
    void *thread_func(void *msg);
    sem_t sem;//信号量
    sem_t sem_add;//增加的信号量
    
    
    #define MSG_SIZE 512
    
    
    int main()
    {
    	int res = -1;
    	pthread_t thread;
    	void *thread_result = NULL;
    	char msg[MSG_SIZE];
    	//初始化信号量,初始值为0
    	res = sem_init(&sem, 0, 0);
    	if(res == -1)
    	{
    		perror("semaphore intitialization failed\n");
    		exit(EXIT_FAILURE);
    	}
    	//初始化信号量,初始值为1
    	res = sem_init(&sem_add, 0, 1);
    	if(res == -1)
    	{
    		perror("semaphore intitialization failed\n");
    		exit(EXIT_FAILURE);
    	}
    	//创建线程,并把msg作为线程函数的参数
    	res = pthread_create(&thread, NULL, thread_func, msg);
    	if(res != 0)
    	{
    		perror("pthread_create failed\n");
    		exit(EXIT_FAILURE);
    	}
    	//输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n”
    	printf("Input some text. Enter 'end'to finish...\n");
    	
    	sem_wait(&sem_add);
    	while(strcmp("end\n", msg) != 0)
    	{
    		if(strncmp("TEST", msg, 4) == 0)
    		{
    			strcpy(msg, "copy_data\n");
    			sem_post(&sem);
    			//把sem_add的值减1,即等待子线程处理完成
    			sem_wait(&sem_add);
    		}
    		fgets(msg, MSG_SIZE, stdin);
    		//把信号量加1
    		sem_post(&sem);
    		//把sem_add的值减1,即等待子线程处理完成
    		sem_wait(&sem_add);
    	}
    
    
    	printf("Waiting for thread to finish...\n");
    	//等待子线程结束
    	res = pthread_join(thread, &thread_result);
    	if(res != 0)
    	{
    		perror("pthread_join failed\n");
    		exit(EXIT_FAILURE);
    	}
    	printf("Thread joined\n");
    	//清理信号量
    	sem_destroy(&sem);
    	sem_destroy(&sem_add);
    	exit(EXIT_SUCCESS);
    }
    
    
    void* thread_func(void *msg)
    {
    	char *ptr = msg;
    	//把信号量减1
    	sem_wait(&sem);
    	while(strcmp("end\n", msg) != 0)
    	{
    		int i = 0;
    		//把小写字母变成大写
    		for(; ptr[i] != '\0'; ++i)
    		{
    			if(ptr[i] >= 'a' && ptr[i] <= 'z')
    			{
    				ptr[i] -= 'a' - 'A';
    			}
    		}
    		printf("You input %d characters\n", i-1);
    		printf("To Uppercase: %s\n", ptr);
    		//把信号量加1,表明子线程处理完成
    		sem_post(&sem_add);
    		//把信号量减1
    		sem_wait(&sem);
    	}
    	sem_post(&sem_add);
    	//退出线程
    	pthread_exit(NULL);
    }
    其运行结果如下:


    分析:这里我们多使用了一个信号量sem_add,并把它的初值赋为1,在主线程在使用sem_wait来等待子线程处理完全,由于它的初值为1,所以主线程第一次调用sem_wait总是立即返回,而第二次调用则需要等待子线程处理完成之后。而在子线程中,若处理完成就会马上使用sem_post来增加信号量的值,使主线程中的sem_wait马上返回并执行紧接下面的代码。从运行结果来看,运行终于正常了。注意,在线程函数中,信号量sem和sem_add使用sem_wait和sem_post函数的次序,它们的次序不能错乱,否则在输入end时,可能运行不正常,子线程不能正常退出,从而导致程序不能退出。

    至于使用互斥量的方法,将会在下篇文章:Linux多线程——使用互斥量同步线程中详细介绍。

    展开全文
  • Linux线程同步

    2019-06-14 13:45:21
    线程同步 同步: 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。 竞态条件: 因为时序问题,而导致程序异常,我们称之为竞态条件。 没有同步导致的饥饿问题...

    线程同步

    同步: 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。
    竞态条件: 因为时序问题,而导致程序异常,我们称之为竞态条件。

    没有同步导致的饥饿问题:
    假如有一个线程,它非常强势,它一直都在申请锁,然后申请完了释放锁,释放完了又不给别的线程留机会,自己又去申请锁,那么此时如果有线程想要访问临界资源就必须等它彻底走了,那么有越来越多的线程想要访问临界资源,这个线程又一直不给让路,那么就会导致其他线程长时间得不到CPU资源从而导致其他线程的饥饿问题。所以这种情况就应该要加上同步机制,让这个线程申请一次释放之后就排在等待队列最后进行等待按顺序访问临界资源,让其他线程进行临界资源的访问。

    没有同步导致的效率问题:
    假如,有一个读线程,一个写线程,它们往管道里进行数据的读写,那么如果写线程写满了,该让读线程来读取数据了,但是此时读线程不知道已经写好了,一直不去读,写线程就会不断地去检测管道里是否有空间可以写数据,这样就会造成效率的低下,因此此时需要加上同步机制,当写线程写完的条件满足时,通知读线程来进行数据的读取,同样的,读线程读完的条件满足时,通知写线程来进行数据的写入,这样就达到了按照一定的顺序访问临界资源。

    所以需要引入条件变量相关函数来解决线程的等待和通知问题:

    1. 条件不满足时,就挂起等待
    2. 条件满足时通过条件变量就会通知另外的进程来执行其他的操作
    初始化:pthread_cond_init
    int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
    

    参数:
    cond: 要初始化的条件变量
    attr: 属性,一般设置为NULL

    销毁:pthread_cond_destroy
    int pthread_cond_destroy(pthread_cond_t *cond)
    
    等待条件满足:pthread_cond_wait
    int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
    

    参数:
    cond: 要在这个条件变量上等待(把当前线程挂起)
    mutex: 互斥量(把当前线程拿到的锁释放)

    为什么第二个参数要传互斥量?
    因为等待条件满足是在临界区里等待的,如果某个线程直接进行等待,那么就会抱着锁去等待,此时,其他线程也无法进入临界资源。

    • 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
    • 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。
      在这里插入图片描述
    唤醒等待:pthread_cond_signal/phread_cond_broadcast
    int pthread_cond_broadcast(pthread_cond_t *cond);
    int pthread_cond_signal(pthread_cond_t *cond);
    

    pthread_cond_broadcast唤醒多个线程
    pthread_cond_signal唤醒一个线程

    条件变量使用规范

    等待条件代码

    pthread_mutex_lock(&lock);     ------上锁
    while(条件不成立)
    {
    	pthread_cond_wait(&cond);  ------条件不成立时挂起等待(注意是while}
    进行操作......                  ------修改、操作
    pthread_mutex_unlock(&lock);   ------解锁
    

    给条件发信号代码

    pthread_mutex_lock(&lock);     ------上锁
    设置条件为真...                 ------符合条件
    pthread_cond_signal(&cond);    ------唤醒
    pthread_mutex_unlock(&lock);   ------解锁
    

    示例代码

    在这里插入图片描述

    展开全文
  • windows系统多线程同步机制原理总结 同步问题是开发过程中遇到的重要问题之一。同步是要保证在并发执行的环境中各个控制流可以有序地执行,包括对于资源的共享或互斥访问,以及代码功能的逻辑顺序。 为了保证多线程...

    windows系统多线程同步机制原理总结

    同步问题是开发过程中遇到的重要问题之一。同步是要保证在并发执行的环境中各个控制流可以有序地执行,包括对于资源的共享或互斥访问,以及代码功能的逻辑顺序。
    为了保证多线程间的同步,Windows操作系统提供了一系列的机制:事件、互斥体、信号量等等。本文主要基于对《Windows内核原理与实现》一书相关章节的整理并结合自己的理解介绍同步机制的大概实现原理,有任何不妥的地方,希望大家能够不吝指出。

    Windows线程调度

    我们先来简单的说以下Windows的线程调度,这是理解后续东西的基础。Windows的调度算法是一个抢占式的、支持多处理器的优先级调度算法。这种调度算法将处理器的时间分成一个个的时间片段,每个时间片段分给不同的线程去执行线程中的指令,直到时间片用完。显然,这样做使得处理器的执行不是按照一个事先知道的顺序依次进行;操作系统在调度线程时需要一个时钟中断来获得对处理器的控制权,从而引导处理器去执行操作系统想要它执行的目标线程的指令。

    中断

    说到Windows的线程调度是通过中断来切换线程的,那么到底什么是中断呢?我们这样想:处理器在执行指令时肯定是顺序执行指令流,那么它怎么处理一些事先无法预知、运行时才发生的事件呢?比如用户某一时刻突然敲下键盘。一种办法是处理器足够频繁地去挨个检查所有可能发生的事件是否发生了,这种方法显然很笨;另一种方法就是使用中断。
    中断其实包含硬件中断和软件中断,说说硬件中断(软件中断其实是模拟的硬件中断)。硬件中断是外部设备在需要通知处理器去做一件事的时候向处理器的特定管脚(NMI和INTR)发送数据,处理器每执行完一条指令都会去检查这个管脚的状态,看看是否有中断发生。通过中断机制,处理器就不需要去挨个设备的检查了,只需要统一的检查是否发生了中断。那么,处理器在得知发生中断后,又怎么知道发生了什么中断?以及怎么去处理中断呢?
    原来,每个中断都有一个中断编号,也称为中断向量。外部设备在触发处理器中断时会发生相应的中断向量。除此之外,操作系统中维护一个中断描述符表(IDT),这个表将每个中断向量与中断服务例程(用来处理该中断的一段程序)关联起来。处理器根据中断向量查询IDT,从而找到中断服务例程的地址,去执行中断服务例程,完成对中断的响应。

    同步机制的实现

    现在我们明白了操作系统是怎么调度线程的了,这是这种调度方式使得线程的同步有了很多不同的方式。那怎么实现呢?

    1. 不依赖于线程调度的同步机制

    第一种做法是对中断下手。需要申明,这种方法只对单处理器有效。单处理器情况下,导致资源争用的罪魁祸首是线程调度,它使得一个处理器分时并行地干几件事,就好像有多个处理器一样。那不让线程调度不就行了?对!前面说到了,线程调度是依靠中断实现的,那就从中断下手。处理器在处理中断时,并不是什么中断都处理,它会判断当前条件下是否需要处理某一中断。而Windows提供了一套中断级别定义方案,叫做中断请求级别(IRQL)。它使用0~31来表示优先级,数值越大,优先级越高;处理器任何时候都运行在一个级别上,且只能被更高级别的中断打断。感兴趣的同学可以去查查IRQL到底包含哪些级别,本文只说其中一个级别——DISPATCH_LEVEL,从名称中可以大致猜到这是线程调度时所在的级别。因此,如果在访问需要同步的资源之前,将处理器的运行级别提高到DISPATCH_LEVEL或更高的IRQL,这时操作系统就不会调度线程了,对单处理器而言就不存在其他线程同时访问资源的情况,也就实现了目标。

    下面划重点:自旋锁我们都知道。它的实现就利用了这种方式,因此,线程在自旋等待时不会有线程切换。也正是因为不会有线程切换,省去了切换的耗时,因此它很适合预期等待时间很短的情况使用。

    2. 基于线程调度的同步机制

    第二种方法我们不去干涉线程调度。那么要想让不同线程能够协调起来,我们需要一个全局的东西去协调它们。这个东西就是同步对象,也称为分发器对象。无论是互斥体还是信号量还是其他什么同步方式,它们都需要针对一个确定的分发器对象。一个分发器对象有基本的两个状态:有信号和无信号状态,分发器对象初始化时为有信号状态。当一个线程执行某种同步方式时,它去查看指定分发器对象是否有信号,若有信号,该线程继续执行,同时该分发器对象的状态变为无信号;再有别的线程去查看该分发器对象时,发现状态为无信号,此时该线程进入等待状态(睡眠),操作系统的线程调度将不再调度该线程。这就实现了只能一个线程访问同一资源。

    那么,当第一个线程执行完了后,分发器对象的状态重新设定为有信号后,其他在等待该分发器对象的线程又怎么知道呢?这就涉及到线程对象和分发器对象的数据结构。在线程对象和分发器对象中都有同样的一个数据成员:一个指向某一链表头节点的指针,而这个链表是一个等待块对象链表。每个等待块对象都记录了哪个线程在等待哪个对象。是的,等待块对象会同时加入到两个链表中:一个是线程对象中的链表,一个是分发器对象中的链表。
    现在我们回到之前的情形,当别的线程去查看该分发器对象时,发现状态为无信号,此时实例化一个等待块对象,它记录了当前线程在等当前分发器对象,这个等待块对象都添加到了线程对象和分发器对象的链表中;当分发器对象重新变为有信号后,它会去唤醒等待块链表中记录的线程对象,此时等待的线程的状态变成延迟的就绪状态,等待操作系统线程调度器来调度。

    分发器对象有很多种,具体可分为:事件、突变体、信号量、进程、线程、队列、门、定时器。有些名字听起来是不是很熟悉?是的,它们都对应着不同的同步方式:比如事件就对应同步中的事件、突变体对应互斥体…这些分发器对象在数据结构上分为两部分:对象头部和对象体。不同分发器的对象头部是一样的(对象头部的第三个数据正是等待块链表的头指针),对象体因功能不同而不同(类似于面向对象里的多态性)。对象体不同,正是与之对应的同步方式功能有所不同的本质原因。比如说突变体对象的对象体中有一个所有者概念,用于表示当前拥有该突变体对象的线程(我认为这就是互斥锁能作为递归锁的原因)。

    需要说一下,这些同步方式都需要处理器提供支持。如果处理器不提供原子运算的功能,那么什么事件、信号量等等都是空谈,因为至少在对分发器对象进行判断的时候必须要保证其过程是原子操作。现代处理器会提供一些原子运算指令,比如在Inter x86指令体系中,有些运算指令加上lock前缀就可以保证其原子性,lock前缀指令使用的两个条件:

    1. 指令的目标操作数必须一个是内存操作数;
    2. 仅适用于ADD、ADC、AND、BTC、BTR、BTS、CMPXCHG、CMPXCH8B、DEC、INC、NEG、NOT、OR、SBB、SUB、XOR、XADD、XCHG。

    最后总结一下,我们常见的线程同步方式,如事件、互斥体等,它们的实现都是依靠于分发器对象。操作系统通过查看分发器对象的状态,判断一个线程是等待还是执行。

    展开全文
  • 线程同步机制的几种方法总结与对比 需要线程同步的原因: 当有个线程同时访问一个共享内存里面的变量时,有时会出现一个线程正在修改该变量的值,而其他的线程正在读取数据,可能就会导致错误。   实现线程...
  • Linux sem 信号量 线程同步机制
  • linux C线程同步机制有很: 互斥锁基本操作函数: 初始化互斥锁: pthread_mutex_init(); 销毁互斥锁: pthread_mutex_destroy(); 阻塞申请互斥锁: pthread_mutex_lock(); 释放互斥锁: pthread_mutex_...
  • linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。 一、互斥锁(mutex) 通过锁机制实现线程间的同步。 初始化锁。在Linux下,线程的互斥量数据类型是pthread_mutex_t。在使用前,要对它...
  • Linux多线程同步

    2016-04-13 22:51:46
    Linux多线程同步   典型的UNIX系统都支持一个进程创建多个线程(thread)。在LINUX基础中提到,Linux以进程为单位组织操作,Linux中的线程也都基于进程。尽管实现方式有异于其它的UNIX系统,但Linux...
  • linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。一、互斥锁(mutex)通过锁机制实现线程间的同步。初始化锁。在Linux下,线程的互斥量数据类型是pthread_mutex_t。在使用前,要对它进行...
  • 背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题?通过多线程模拟多窗口售票为例:#include <iostream> #include<pthread.h> #include<stdio.h> #include<stdlib.h> #...
  • linux多线程-----同步机制(互斥量、读写锁、条件变量)
  • linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。 1)互斥锁(mutex)  通过锁机制实现线程间的同步。同一时刻只允许一个线程执行一个关键部分的代码。 int pthread_mutex_init...
  • 线程同步多线程编程中,解决共享资源冲突的问题 进程同步:多进程编程中,解决共享资源冲突的问题 但是部分同学对线程同步和进程同步研究得不够深入,比如互斥锁和条件变量能不能同时用于线程同步和进程同步,...
  • 这里主要介绍Posix中两种线程同步机制,分别为互斥锁和信号量。这两个同步机制可以通过互相调用对方来实现,但互斥锁更适用于同时可用的资源是唯一的情况;信号量更适用于同时可用的资源为个的情况。
  •   1、确保线程互斥访问同步代码;   2、保证共享变量的修改能够及时可见;   3、有效解决指令重排序问题。   synrhronized关键字简洁、清晰、语义明确,因此即使有了Lock接口,使用的还是非常广泛。其应用层...
  • Linux线程浅析[线程同步和互斥之线程读写锁] 读写锁的出现是为了解决互斥锁的弊端 使用上述Linux线程浅析[线程同步和互斥之线程互斥锁]的案例来进行解释吧,即针对上述案例中的银行存取款的互斥锁,当一个账户...
  • 2无锡梅格科技有限公司 江苏无锡 214122)摘要 介绍linux线程的基本概念,线程间的互斥和同步机制,分析了linuxpthread库的API函数,并结合一个例子阐述多线程编程的核心技术,最后总结出多线程编程应注意的事项。...
  • Linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量、信号量和读写锁。  下面是思维导图: 简介 进程— 资源分配的最小单位 线程— 程序执行的最小单位 进程是一个程序的一个实例,拥有自...
1 2 3 4 5 ... 20
收藏数 85,390
精华内容 34,156
关键字:

linux多线程同步 机制